##// END OF EJS Templates
landing-rev: fixes #4102, use branches instead of landing tip refs by default....
marcink -
r3881:95b6f937 default
parent child Browse files
Show More
@@ -1,117 +1,122 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-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
21
22 import random
22 import random
23 import pytest
23
24
24 from rhodecode.api.utils import get_origin
25 from rhodecode.api.utils import get_origin
25 from rhodecode.lib.ext_json import json
26 from rhodecode.lib.ext_json import json
26
27
27
28
29 def jsonify(obj):
30 return json.loads(json.dumps(obj))
31
32
28 API_URL = '/_admin/api'
33 API_URL = '/_admin/api'
29
34
30
35
31 def assert_call_ok(id_, given):
36 def assert_call_ok(id_, given):
32 expected = jsonify({
37 expected = jsonify({
33 'id': id_,
38 'id': id_,
34 'error': None,
39 'error': None,
35 'result': None
40 'result': None
36 })
41 })
37 given = json.loads(given)
42 given = json.loads(given)
38
43
39 assert expected['id'] == given['id']
44 assert expected['id'] == given['id']
40 assert expected['error'] == given['error']
45 assert expected['error'] == given['error']
41 return given['result']
46 return given['result']
42
47
43
48
44 def assert_ok(id_, expected, given):
49 def assert_ok(id_, expected, given):
50 given = json.loads(given)
51 if given.get('error'):
52 pytest.fail("Unexpected ERROR in success response: {}".format(given['error']))
53
45 expected = jsonify({
54 expected = jsonify({
46 'id': id_,
55 'id': id_,
47 'error': None,
56 'error': None,
48 'result': expected
57 'result': expected
49 })
58 })
50 given = json.loads(given)
59
51 assert expected == given
60 assert expected == given
52
61
53
62
54 def assert_error(id_, expected, given):
63 def assert_error(id_, expected, given):
55 expected = jsonify({
64 expected = jsonify({
56 'id': id_,
65 'id': id_,
57 'error': expected,
66 'error': expected,
58 'result': None
67 'result': None
59 })
68 })
60 given = json.loads(given)
69 given = json.loads(given)
61 assert expected == given
70 assert expected == given
62
71
63
72
64 def jsonify(obj):
65 return json.loads(json.dumps(obj))
66
67
68 def build_data(apikey, method, **kw):
73 def build_data(apikey, method, **kw):
69 """
74 """
70 Builds API data with given random ID
75 Builds API data with given random ID
71 """
76 """
72 random_id = random.randrange(1, 9999)
77 random_id = random.randrange(1, 9999)
73 return random_id, json.dumps({
78 return random_id, json.dumps({
74 "id": random_id,
79 "id": random_id,
75 "api_key": apikey,
80 "api_key": apikey,
76 "method": method,
81 "method": method,
77 "args": kw
82 "args": kw
78 })
83 })
79
84
80
85
81 def api_call(app, params, status=None):
86 def api_call(app, params, status=None):
82 response = app.post(
87 response = app.post(
83 API_URL, content_type='application/json', params=params, status=status)
88 API_URL, content_type='application/json', params=params, status=status)
84 return response
89 return response
85
90
86
91
87 def crash(*args, **kwargs):
92 def crash(*args, **kwargs):
88 raise Exception('Total Crash !')
93 raise Exception('Total Crash !')
89
94
90
95
91 def expected_permissions(object_with_permissions):
96 def expected_permissions(object_with_permissions):
92 """
97 """
93 Returns the expected permissions structure for the given object.
98 Returns the expected permissions structure for the given object.
94
99
95 The object is expected to be a `Repository`, `RepositoryGroup`,
100 The object is expected to be a `Repository`, `RepositoryGroup`,
96 or `UserGroup`. They all implement the same permission handling
101 or `UserGroup`. They all implement the same permission handling
97 API.
102 API.
98 """
103 """
99 permissions = []
104 permissions = []
100 for _user in object_with_permissions.permissions():
105 for _user in object_with_permissions.permissions():
101 user_data = {
106 user_data = {
102 'name': _user.username,
107 'name': _user.username,
103 'permission': _user.permission,
108 'permission': _user.permission,
104 'origin': get_origin(_user),
109 'origin': get_origin(_user),
105 'type': "user",
110 'type': "user",
106 }
111 }
107 permissions.append(user_data)
112 permissions.append(user_data)
108
113
109 for _user_group in object_with_permissions.permission_user_groups():
114 for _user_group in object_with_permissions.permission_user_groups():
110 user_group_data = {
115 user_group_data = {
111 'name': _user_group.users_group_name,
116 'name': _user_group.users_group_name,
112 'permission': _user_group.permission,
117 'permission': _user_group.permission,
113 'origin': get_origin(_user_group),
118 'origin': get_origin(_user_group),
114 'type': "user_group",
119 'type': "user_group",
115 }
120 }
116 permissions.append(user_group_data)
121 permissions.append(user_group_data)
117 return permissions
122 return permissions
@@ -1,2313 +1,2327 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 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):
620 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
620 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
621
621
622 try:
622 try:
623 # 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.
624 _scm = repo.scm_instance()
624 _scm = repo.scm_instance()
625 if _scm.is_empty():
625 if _scm.is_empty():
626 return []
626 return []
627 except RepositoryError:
627 except RepositoryError:
628 log.exception("Exception occurred while trying to get repo nodes")
628 log.exception("Exception occurred while trying to get repo nodes")
629 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
629 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
630
630
631 try:
631 try:
632 # 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.
633 # 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.
634 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
634 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
635 log.debug(
635 log.debug(
636 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
636 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
637 'with caching: %s[TTL: %ss]' % (
637 'with caching: %s[TTL: %ss]' % (
638 repo_id, commit_id, cache_on, cache_seconds or 0))
638 repo_id, commit_id, cache_on, cache_seconds or 0))
639
639
640 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')
641 return tree_files
641 return tree_files
642
642
643 except Exception:
643 except Exception:
644 log.exception("Exception occurred while trying to get repo nodes")
644 log.exception("Exception occurred while trying to get repo nodes")
645 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
645 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
646
646
647
647
648 @jsonrpc_method()
648 @jsonrpc_method()
649 def get_repo_refs(request, apiuser, repoid):
649 def get_repo_refs(request, apiuser, repoid):
650 """
650 """
651 Returns a dictionary of current references. It returns
651 Returns a dictionary of current references. It returns
652 bookmarks, branches, closed_branches, and tags for given repository
652 bookmarks, branches, closed_branches, and tags for given repository
653
653
654 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`.
655
655
656 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,
657 or users with at least read rights to |repos|.
657 or users with at least read rights to |repos|.
658
658
659 :param apiuser: This is filled automatically from the |authtoken|.
659 :param apiuser: This is filled automatically from the |authtoken|.
660 :type apiuser: AuthUser
660 :type apiuser: AuthUser
661 :param repoid: The repository name or repository ID.
661 :param repoid: The repository name or repository ID.
662 :type repoid: str or int
662 :type repoid: str or int
663
663
664 Example output:
664 Example output:
665
665
666 .. code-block:: bash
666 .. code-block:: bash
667
667
668 id : <id_given_in_input>
668 id : <id_given_in_input>
669 "result": {
669 "result": {
670 "bookmarks": {
670 "bookmarks": {
671 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
671 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
672 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
672 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
673 },
673 },
674 "branches": {
674 "branches": {
675 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
675 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
676 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
676 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
677 },
677 },
678 "branches_closed": {},
678 "branches_closed": {},
679 "tags": {
679 "tags": {
680 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
680 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
681 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
681 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
682 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
682 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
683 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
683 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
684 }
684 }
685 }
685 }
686 error: null
686 error: null
687 """
687 """
688
688
689 repo = get_repo_or_error(repoid)
689 repo = get_repo_or_error(repoid)
690 if not has_superadmin_permission(apiuser):
690 if not has_superadmin_permission(apiuser):
691 _perms = ('repository.admin', 'repository.write', 'repository.read',)
691 _perms = ('repository.admin', 'repository.write', 'repository.read',)
692 validate_repo_permissions(apiuser, repoid, repo, _perms)
692 validate_repo_permissions(apiuser, repoid, repo, _perms)
693
693
694 try:
694 try:
695 # 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.
696 vcs_instance = repo.scm_instance()
696 vcs_instance = repo.scm_instance()
697 refs = vcs_instance.refs()
697 refs = vcs_instance.refs()
698 return refs
698 return refs
699 except Exception:
699 except Exception:
700 log.exception("Exception occurred while trying to get repo refs")
700 log.exception("Exception occurred while trying to get repo refs")
701 raise JSONRPCError(
701 raise JSONRPCError(
702 'failed to get repo: `%s` references' % repo.repo_name
702 'failed to get repo: `%s` references' % repo.repo_name
703 )
703 )
704
704
705
705
706 @jsonrpc_method()
706 @jsonrpc_method()
707 def create_repo(
707 def create_repo(
708 request, apiuser, repo_name, repo_type,
708 request, apiuser, repo_name, repo_type,
709 owner=Optional(OAttr('apiuser')),
709 owner=Optional(OAttr('apiuser')),
710 description=Optional(''),
710 description=Optional(''),
711 private=Optional(False),
711 private=Optional(False),
712 clone_uri=Optional(None),
712 clone_uri=Optional(None),
713 push_uri=Optional(None),
713 push_uri=Optional(None),
714 landing_rev=Optional('rev:tip'),
714 landing_rev=Optional(None),
715 enable_statistics=Optional(False),
715 enable_statistics=Optional(False),
716 enable_locking=Optional(False),
716 enable_locking=Optional(False),
717 enable_downloads=Optional(False),
717 enable_downloads=Optional(False),
718 copy_permissions=Optional(False)):
718 copy_permissions=Optional(False)):
719 """
719 """
720 Creates a repository.
720 Creates a repository.
721
721
722 * If the repository name contains "/", repository will be created inside
722 * If the repository name contains "/", repository will be created inside
723 a repository group or nested repository groups
723 a repository group or nested repository groups
724
724
725 For example "foo/bar/repo1" will create |repo| called "repo1" inside
725 For example "foo/bar/repo1" will create |repo| called "repo1" inside
726 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
727 the last repository group ("bar" in this example)
727 the last repository group ("bar" in this example)
728
728
729 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
730 permissions to create repositories, or write permissions to
730 permissions to create repositories, or write permissions to
731 parent repository groups.
731 parent repository groups.
732
732
733 :param apiuser: This is filled automatically from the |authtoken|.
733 :param apiuser: This is filled automatically from the |authtoken|.
734 :type apiuser: AuthUser
734 :type apiuser: AuthUser
735 :param repo_name: Set the repository name.
735 :param repo_name: Set the repository name.
736 :type repo_name: str
736 :type repo_name: str
737 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
737 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
738 :type repo_type: str
738 :type repo_type: str
739 :param owner: user_id or username
739 :param owner: user_id or username
740 :type owner: Optional(str)
740 :type owner: Optional(str)
741 :param description: Set the repository description.
741 :param description: Set the repository description.
742 :type description: Optional(str)
742 :type description: Optional(str)
743 :param private: set repository as private
743 :param private: set repository as private
744 :type private: bool
744 :type private: bool
745 :param clone_uri: set clone_uri
745 :param clone_uri: set clone_uri
746 :type clone_uri: str
746 :type clone_uri: str
747 :param push_uri: set push_uri
747 :param push_uri: set push_uri
748 :type push_uri: str
748 :type push_uri: str
749 :param landing_rev: <rev_type>:<rev>
749 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
750 :type landing_rev: str
750 :type landing_rev: str
751 :param enable_locking:
751 :param enable_locking:
752 :type enable_locking: bool
752 :type enable_locking: bool
753 :param enable_downloads:
753 :param enable_downloads:
754 :type enable_downloads: bool
754 :type enable_downloads: bool
755 :param enable_statistics:
755 :param enable_statistics:
756 :type enable_statistics: bool
756 :type enable_statistics: bool
757 :param copy_permissions: Copy permission from group in which the
757 :param copy_permissions: Copy permission from group in which the
758 repository is being created.
758 repository is being created.
759 :type copy_permissions: bool
759 :type copy_permissions: bool
760
760
761
761
762 Example output:
762 Example output:
763
763
764 .. code-block:: bash
764 .. code-block:: bash
765
765
766 id : <id_given_in_input>
766 id : <id_given_in_input>
767 result: {
767 result: {
768 "msg": "Created new repository `<reponame>`",
768 "msg": "Created new repository `<reponame>`",
769 "success": true,
769 "success": true,
770 "task": "<celery task id or None if done sync>"
770 "task": "<celery task id or None if done sync>"
771 }
771 }
772 error: null
772 error: null
773
773
774
774
775 Example error output:
775 Example error output:
776
776
777 .. code-block:: bash
777 .. code-block:: bash
778
778
779 id : <id_given_in_input>
779 id : <id_given_in_input>
780 result : null
780 result : null
781 error : {
781 error : {
782 'failed to create repository `<repo_name>`'
782 'failed to create repository `<repo_name>`'
783 }
783 }
784
784
785 """
785 """
786
786
787 owner = validate_set_owner_permissions(apiuser, owner)
787 owner = validate_set_owner_permissions(apiuser, owner)
788
788
789 description = Optional.extract(description)
789 description = Optional.extract(description)
790 copy_permissions = Optional.extract(copy_permissions)
790 copy_permissions = Optional.extract(copy_permissions)
791 clone_uri = Optional.extract(clone_uri)
791 clone_uri = Optional.extract(clone_uri)
792 push_uri = Optional.extract(push_uri)
792 push_uri = Optional.extract(push_uri)
793 landing_commit_ref = Optional.extract(landing_rev)
794
793
795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
794 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
796 if isinstance(private, Optional):
795 if isinstance(private, Optional):
797 private = defs.get('repo_private') or Optional.extract(private)
796 private = defs.get('repo_private') or Optional.extract(private)
798 if isinstance(repo_type, Optional):
797 if isinstance(repo_type, Optional):
799 repo_type = defs.get('repo_type')
798 repo_type = defs.get('repo_type')
800 if isinstance(enable_statistics, Optional):
799 if isinstance(enable_statistics, Optional):
801 enable_statistics = defs.get('repo_enable_statistics')
800 enable_statistics = defs.get('repo_enable_statistics')
802 if isinstance(enable_locking, Optional):
801 if isinstance(enable_locking, Optional):
803 enable_locking = defs.get('repo_enable_locking')
802 enable_locking = defs.get('repo_enable_locking')
804 if isinstance(enable_downloads, Optional):
803 if isinstance(enable_downloads, Optional):
805 enable_downloads = defs.get('repo_enable_downloads')
804 enable_downloads = defs.get('repo_enable_downloads')
806
805
806 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
807 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
808 ref_choices = list(set(ref_choices + [landing_ref]))
809
810 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
811
807 schema = repo_schema.RepoSchema().bind(
812 schema = repo_schema.RepoSchema().bind(
808 repo_type_options=rhodecode.BACKENDS.keys(),
813 repo_type_options=rhodecode.BACKENDS.keys(),
814 repo_ref_options=ref_choices,
809 repo_type=repo_type,
815 repo_type=repo_type,
810 # user caller
816 # user caller
811 user=apiuser)
817 user=apiuser)
812
818
813 try:
819 try:
814 schema_data = schema.deserialize(dict(
820 schema_data = schema.deserialize(dict(
815 repo_name=repo_name,
821 repo_name=repo_name,
816 repo_type=repo_type,
822 repo_type=repo_type,
817 repo_owner=owner.username,
823 repo_owner=owner.username,
818 repo_description=description,
824 repo_description=description,
819 repo_landing_commit_ref=landing_commit_ref,
825 repo_landing_commit_ref=landing_commit_ref,
820 repo_clone_uri=clone_uri,
826 repo_clone_uri=clone_uri,
821 repo_push_uri=push_uri,
827 repo_push_uri=push_uri,
822 repo_private=private,
828 repo_private=private,
823 repo_copy_permissions=copy_permissions,
829 repo_copy_permissions=copy_permissions,
824 repo_enable_statistics=enable_statistics,
830 repo_enable_statistics=enable_statistics,
825 repo_enable_downloads=enable_downloads,
831 repo_enable_downloads=enable_downloads,
826 repo_enable_locking=enable_locking))
832 repo_enable_locking=enable_locking))
827 except validation_schema.Invalid as err:
833 except validation_schema.Invalid as err:
828 raise JSONRPCValidationError(colander_exc=err)
834 raise JSONRPCValidationError(colander_exc=err)
829
835
830 try:
836 try:
831 data = {
837 data = {
832 'owner': owner,
838 'owner': owner,
833 'repo_name': schema_data['repo_group']['repo_name_without_group'],
839 'repo_name': schema_data['repo_group']['repo_name_without_group'],
834 'repo_name_full': schema_data['repo_name'],
840 'repo_name_full': schema_data['repo_name'],
835 'repo_group': schema_data['repo_group']['repo_group_id'],
841 'repo_group': schema_data['repo_group']['repo_group_id'],
836 'repo_type': schema_data['repo_type'],
842 'repo_type': schema_data['repo_type'],
837 'repo_description': schema_data['repo_description'],
843 'repo_description': schema_data['repo_description'],
838 'repo_private': schema_data['repo_private'],
844 'repo_private': schema_data['repo_private'],
839 'clone_uri': schema_data['repo_clone_uri'],
845 'clone_uri': schema_data['repo_clone_uri'],
840 'push_uri': schema_data['repo_push_uri'],
846 'push_uri': schema_data['repo_push_uri'],
841 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
847 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
842 'enable_statistics': schema_data['repo_enable_statistics'],
848 'enable_statistics': schema_data['repo_enable_statistics'],
843 'enable_locking': schema_data['repo_enable_locking'],
849 'enable_locking': schema_data['repo_enable_locking'],
844 'enable_downloads': schema_data['repo_enable_downloads'],
850 'enable_downloads': schema_data['repo_enable_downloads'],
845 'repo_copy_permissions': schema_data['repo_copy_permissions'],
851 'repo_copy_permissions': schema_data['repo_copy_permissions'],
846 }
852 }
847
853
848 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
854 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
849 task_id = get_task_id(task)
855 task_id = get_task_id(task)
850 # no commit, it's done in RepoModel, or async via celery
856 # no commit, it's done in RepoModel, or async via celery
851 return {
857 return {
852 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
858 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
853 'success': True, # cannot return the repo data here since fork
859 'success': True, # cannot return the repo data here since fork
854 # can be done async
860 # can be done async
855 'task': task_id
861 'task': task_id
856 }
862 }
857 except Exception:
863 except Exception:
858 log.exception(
864 log.exception(
859 u"Exception while trying to create the repository %s",
865 u"Exception while trying to create the repository %s",
860 schema_data['repo_name'])
866 schema_data['repo_name'])
861 raise JSONRPCError(
867 raise JSONRPCError(
862 'failed to create repository `%s`' % (schema_data['repo_name'],))
868 'failed to create repository `%s`' % (schema_data['repo_name'],))
863
869
864
870
865 @jsonrpc_method()
871 @jsonrpc_method()
866 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
872 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
867 description=Optional('')):
873 description=Optional('')):
868 """
874 """
869 Adds an extra field to a repository.
875 Adds an extra field to a repository.
870
876
871 This command can only be run using an |authtoken| with at least
877 This command can only be run using an |authtoken| with at least
872 write permissions to the |repo|.
878 write permissions to the |repo|.
873
879
874 :param apiuser: This is filled automatically from the |authtoken|.
880 :param apiuser: This is filled automatically from the |authtoken|.
875 :type apiuser: AuthUser
881 :type apiuser: AuthUser
876 :param repoid: Set the repository name or repository id.
882 :param repoid: Set the repository name or repository id.
877 :type repoid: str or int
883 :type repoid: str or int
878 :param key: Create a unique field key for this repository.
884 :param key: Create a unique field key for this repository.
879 :type key: str
885 :type key: str
880 :param label:
886 :param label:
881 :type label: Optional(str)
887 :type label: Optional(str)
882 :param description:
888 :param description:
883 :type description: Optional(str)
889 :type description: Optional(str)
884 """
890 """
885 repo = get_repo_or_error(repoid)
891 repo = get_repo_or_error(repoid)
886 if not has_superadmin_permission(apiuser):
892 if not has_superadmin_permission(apiuser):
887 _perms = ('repository.admin',)
893 _perms = ('repository.admin',)
888 validate_repo_permissions(apiuser, repoid, repo, _perms)
894 validate_repo_permissions(apiuser, repoid, repo, _perms)
889
895
890 label = Optional.extract(label) or key
896 label = Optional.extract(label) or key
891 description = Optional.extract(description)
897 description = Optional.extract(description)
892
898
893 field = RepositoryField.get_by_key_name(key, repo)
899 field = RepositoryField.get_by_key_name(key, repo)
894 if field:
900 if field:
895 raise JSONRPCError('Field with key '
901 raise JSONRPCError('Field with key '
896 '`%s` exists for repo `%s`' % (key, repoid))
902 '`%s` exists for repo `%s`' % (key, repoid))
897
903
898 try:
904 try:
899 RepoModel().add_repo_field(repo, key, field_label=label,
905 RepoModel().add_repo_field(repo, key, field_label=label,
900 field_desc=description)
906 field_desc=description)
901 Session().commit()
907 Session().commit()
902 return {
908 return {
903 'msg': "Added new repository field `%s`" % (key,),
909 'msg': "Added new repository field `%s`" % (key,),
904 'success': True,
910 'success': True,
905 }
911 }
906 except Exception:
912 except Exception:
907 log.exception("Exception occurred while trying to add field to repo")
913 log.exception("Exception occurred while trying to add field to repo")
908 raise JSONRPCError(
914 raise JSONRPCError(
909 'failed to create new field for repository `%s`' % (repoid,))
915 'failed to create new field for repository `%s`' % (repoid,))
910
916
911
917
912 @jsonrpc_method()
918 @jsonrpc_method()
913 def remove_field_from_repo(request, apiuser, repoid, key):
919 def remove_field_from_repo(request, apiuser, repoid, key):
914 """
920 """
915 Removes an extra field from a repository.
921 Removes an extra field from a repository.
916
922
917 This command can only be run using an |authtoken| with at least
923 This command can only be run using an |authtoken| with at least
918 write permissions to the |repo|.
924 write permissions to the |repo|.
919
925
920 :param apiuser: This is filled automatically from the |authtoken|.
926 :param apiuser: This is filled automatically from the |authtoken|.
921 :type apiuser: AuthUser
927 :type apiuser: AuthUser
922 :param repoid: Set the repository name or repository ID.
928 :param repoid: Set the repository name or repository ID.
923 :type repoid: str or int
929 :type repoid: str or int
924 :param key: Set the unique field key for this repository.
930 :param key: Set the unique field key for this repository.
925 :type key: str
931 :type key: str
926 """
932 """
927
933
928 repo = get_repo_or_error(repoid)
934 repo = get_repo_or_error(repoid)
929 if not has_superadmin_permission(apiuser):
935 if not has_superadmin_permission(apiuser):
930 _perms = ('repository.admin',)
936 _perms = ('repository.admin',)
931 validate_repo_permissions(apiuser, repoid, repo, _perms)
937 validate_repo_permissions(apiuser, repoid, repo, _perms)
932
938
933 field = RepositoryField.get_by_key_name(key, repo)
939 field = RepositoryField.get_by_key_name(key, repo)
934 if not field:
940 if not field:
935 raise JSONRPCError('Field with key `%s` does not '
941 raise JSONRPCError('Field with key `%s` does not '
936 'exists for repo `%s`' % (key, repoid))
942 'exists for repo `%s`' % (key, repoid))
937
943
938 try:
944 try:
939 RepoModel().delete_repo_field(repo, field_key=key)
945 RepoModel().delete_repo_field(repo, field_key=key)
940 Session().commit()
946 Session().commit()
941 return {
947 return {
942 'msg': "Deleted repository field `%s`" % (key,),
948 'msg': "Deleted repository field `%s`" % (key,),
943 'success': True,
949 'success': True,
944 }
950 }
945 except Exception:
951 except Exception:
946 log.exception(
952 log.exception(
947 "Exception occurred while trying to delete field from repo")
953 "Exception occurred while trying to delete field from repo")
948 raise JSONRPCError(
954 raise JSONRPCError(
949 'failed to delete field for repository `%s`' % (repoid,))
955 'failed to delete field for repository `%s`' % (repoid,))
950
956
951
957
952 @jsonrpc_method()
958 @jsonrpc_method()
953 def update_repo(
959 def update_repo(
954 request, apiuser, repoid, repo_name=Optional(None),
960 request, apiuser, repoid, repo_name=Optional(None),
955 owner=Optional(OAttr('apiuser')), description=Optional(''),
961 owner=Optional(OAttr('apiuser')), description=Optional(''),
956 private=Optional(False),
962 private=Optional(False),
957 clone_uri=Optional(None), push_uri=Optional(None),
963 clone_uri=Optional(None), push_uri=Optional(None),
958 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
964 landing_rev=Optional(None), fork_of=Optional(None),
959 enable_statistics=Optional(False),
965 enable_statistics=Optional(False),
960 enable_locking=Optional(False),
966 enable_locking=Optional(False),
961 enable_downloads=Optional(False), fields=Optional('')):
967 enable_downloads=Optional(False), fields=Optional('')):
962 """
968 """
963 Updates a repository with the given information.
969 Updates a repository with the given information.
964
970
965 This command can only be run using an |authtoken| with at least
971 This command can only be run using an |authtoken| with at least
966 admin permissions to the |repo|.
972 admin permissions to the |repo|.
967
973
968 * If the repository name contains "/", repository will be updated
974 * If the repository name contains "/", repository will be updated
969 accordingly with a repository group or nested repository groups
975 accordingly with a repository group or nested repository groups
970
976
971 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
977 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
972 called "repo-test" and place it inside group "foo/bar".
978 called "repo-test" and place it inside group "foo/bar".
973 You have to have permissions to access and write to the last repository
979 You have to have permissions to access and write to the last repository
974 group ("bar" in this example)
980 group ("bar" in this example)
975
981
976 :param apiuser: This is filled automatically from the |authtoken|.
982 :param apiuser: This is filled automatically from the |authtoken|.
977 :type apiuser: AuthUser
983 :type apiuser: AuthUser
978 :param repoid: repository name or repository ID.
984 :param repoid: repository name or repository ID.
979 :type repoid: str or int
985 :type repoid: str or int
980 :param repo_name: Update the |repo| name, including the
986 :param repo_name: Update the |repo| name, including the
981 repository group it's in.
987 repository group it's in.
982 :type repo_name: str
988 :type repo_name: str
983 :param owner: Set the |repo| owner.
989 :param owner: Set the |repo| owner.
984 :type owner: str
990 :type owner: str
985 :param fork_of: Set the |repo| as fork of another |repo|.
991 :param fork_of: Set the |repo| as fork of another |repo|.
986 :type fork_of: str
992 :type fork_of: str
987 :param description: Update the |repo| description.
993 :param description: Update the |repo| description.
988 :type description: str
994 :type description: str
989 :param private: Set the |repo| as private. (True | False)
995 :param private: Set the |repo| as private. (True | False)
990 :type private: bool
996 :type private: bool
991 :param clone_uri: Update the |repo| clone URI.
997 :param clone_uri: Update the |repo| clone URI.
992 :type clone_uri: str
998 :type clone_uri: str
993 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
999 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
994 :type landing_rev: str
1000 :type landing_rev: str
995 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1001 :param enable_statistics: Enable statistics on the |repo|, (True | False).
996 :type enable_statistics: bool
1002 :type enable_statistics: bool
997 :param enable_locking: Enable |repo| locking.
1003 :param enable_locking: Enable |repo| locking.
998 :type enable_locking: bool
1004 :type enable_locking: bool
999 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1005 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1000 :type enable_downloads: bool
1006 :type enable_downloads: bool
1001 :param fields: Add extra fields to the |repo|. Use the following
1007 :param fields: Add extra fields to the |repo|. Use the following
1002 example format: ``field_key=field_val,field_key2=fieldval2``.
1008 example format: ``field_key=field_val,field_key2=fieldval2``.
1003 Escape ', ' with \,
1009 Escape ', ' with \,
1004 :type fields: str
1010 :type fields: str
1005 """
1011 """
1006
1012
1007 repo = get_repo_or_error(repoid)
1013 repo = get_repo_or_error(repoid)
1008
1014
1009 include_secrets = False
1015 include_secrets = False
1010 if not has_superadmin_permission(apiuser):
1016 if not has_superadmin_permission(apiuser):
1011 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1017 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1012 else:
1018 else:
1013 include_secrets = True
1019 include_secrets = True
1014
1020
1015 updates = dict(
1021 updates = dict(
1016 repo_name=repo_name
1022 repo_name=repo_name
1017 if not isinstance(repo_name, Optional) else repo.repo_name,
1023 if not isinstance(repo_name, Optional) else repo.repo_name,
1018
1024
1019 fork_id=fork_of
1025 fork_id=fork_of
1020 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1026 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1021
1027
1022 user=owner
1028 user=owner
1023 if not isinstance(owner, Optional) else repo.user.username,
1029 if not isinstance(owner, Optional) else repo.user.username,
1024
1030
1025 repo_description=description
1031 repo_description=description
1026 if not isinstance(description, Optional) else repo.description,
1032 if not isinstance(description, Optional) else repo.description,
1027
1033
1028 repo_private=private
1034 repo_private=private
1029 if not isinstance(private, Optional) else repo.private,
1035 if not isinstance(private, Optional) else repo.private,
1030
1036
1031 clone_uri=clone_uri
1037 clone_uri=clone_uri
1032 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1038 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1033
1039
1034 push_uri=push_uri
1040 push_uri=push_uri
1035 if not isinstance(push_uri, Optional) else repo.push_uri,
1041 if not isinstance(push_uri, Optional) else repo.push_uri,
1036
1042
1037 repo_landing_rev=landing_rev
1043 repo_landing_rev=landing_rev
1038 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1044 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1039
1045
1040 repo_enable_statistics=enable_statistics
1046 repo_enable_statistics=enable_statistics
1041 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1047 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1042
1048
1043 repo_enable_locking=enable_locking
1049 repo_enable_locking=enable_locking
1044 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1050 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1045
1051
1046 repo_enable_downloads=enable_downloads
1052 repo_enable_downloads=enable_downloads
1047 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1053 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1048
1054
1055 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1049 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1056 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1050 request.translate, repo=repo)
1057 request.translate, repo=repo)
1058 ref_choices = list(set(ref_choices + [landing_ref]))
1051
1059
1052 old_values = repo.get_api_data()
1060 old_values = repo.get_api_data()
1053 repo_type = repo.repo_type
1061 repo_type = repo.repo_type
1054 schema = repo_schema.RepoSchema().bind(
1062 schema = repo_schema.RepoSchema().bind(
1055 repo_type_options=rhodecode.BACKENDS.keys(),
1063 repo_type_options=rhodecode.BACKENDS.keys(),
1056 repo_ref_options=ref_choices,
1064 repo_ref_options=ref_choices,
1057 repo_type=repo_type,
1065 repo_type=repo_type,
1058 # user caller
1066 # user caller
1059 user=apiuser,
1067 user=apiuser,
1060 old_values=old_values)
1068 old_values=old_values)
1061 try:
1069 try:
1062 schema_data = schema.deserialize(dict(
1070 schema_data = schema.deserialize(dict(
1063 # we save old value, users cannot change type
1071 # we save old value, users cannot change type
1064 repo_type=repo_type,
1072 repo_type=repo_type,
1065
1073
1066 repo_name=updates['repo_name'],
1074 repo_name=updates['repo_name'],
1067 repo_owner=updates['user'],
1075 repo_owner=updates['user'],
1068 repo_description=updates['repo_description'],
1076 repo_description=updates['repo_description'],
1069 repo_clone_uri=updates['clone_uri'],
1077 repo_clone_uri=updates['clone_uri'],
1070 repo_push_uri=updates['push_uri'],
1078 repo_push_uri=updates['push_uri'],
1071 repo_fork_of=updates['fork_id'],
1079 repo_fork_of=updates['fork_id'],
1072 repo_private=updates['repo_private'],
1080 repo_private=updates['repo_private'],
1073 repo_landing_commit_ref=updates['repo_landing_rev'],
1081 repo_landing_commit_ref=updates['repo_landing_rev'],
1074 repo_enable_statistics=updates['repo_enable_statistics'],
1082 repo_enable_statistics=updates['repo_enable_statistics'],
1075 repo_enable_downloads=updates['repo_enable_downloads'],
1083 repo_enable_downloads=updates['repo_enable_downloads'],
1076 repo_enable_locking=updates['repo_enable_locking']))
1084 repo_enable_locking=updates['repo_enable_locking']))
1077 except validation_schema.Invalid as err:
1085 except validation_schema.Invalid as err:
1078 raise JSONRPCValidationError(colander_exc=err)
1086 raise JSONRPCValidationError(colander_exc=err)
1079
1087
1080 # save validated data back into the updates dict
1088 # save validated data back into the updates dict
1081 validated_updates = dict(
1089 validated_updates = dict(
1082 repo_name=schema_data['repo_group']['repo_name_without_group'],
1090 repo_name=schema_data['repo_group']['repo_name_without_group'],
1083 repo_group=schema_data['repo_group']['repo_group_id'],
1091 repo_group=schema_data['repo_group']['repo_group_id'],
1084
1092
1085 user=schema_data['repo_owner'],
1093 user=schema_data['repo_owner'],
1086 repo_description=schema_data['repo_description'],
1094 repo_description=schema_data['repo_description'],
1087 repo_private=schema_data['repo_private'],
1095 repo_private=schema_data['repo_private'],
1088 clone_uri=schema_data['repo_clone_uri'],
1096 clone_uri=schema_data['repo_clone_uri'],
1089 push_uri=schema_data['repo_push_uri'],
1097 push_uri=schema_data['repo_push_uri'],
1090 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1098 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1091 repo_enable_statistics=schema_data['repo_enable_statistics'],
1099 repo_enable_statistics=schema_data['repo_enable_statistics'],
1092 repo_enable_locking=schema_data['repo_enable_locking'],
1100 repo_enable_locking=schema_data['repo_enable_locking'],
1093 repo_enable_downloads=schema_data['repo_enable_downloads'],
1101 repo_enable_downloads=schema_data['repo_enable_downloads'],
1094 )
1102 )
1095
1103
1096 if schema_data['repo_fork_of']:
1104 if schema_data['repo_fork_of']:
1097 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1105 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1098 validated_updates['fork_id'] = fork_repo.repo_id
1106 validated_updates['fork_id'] = fork_repo.repo_id
1099
1107
1100 # extra fields
1108 # extra fields
1101 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1109 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1102 if fields:
1110 if fields:
1103 validated_updates.update(fields)
1111 validated_updates.update(fields)
1104
1112
1105 try:
1113 try:
1106 RepoModel().update(repo, **validated_updates)
1114 RepoModel().update(repo, **validated_updates)
1107 audit_logger.store_api(
1115 audit_logger.store_api(
1108 'repo.edit', action_data={'old_data': old_values},
1116 'repo.edit', action_data={'old_data': old_values},
1109 user=apiuser, repo=repo)
1117 user=apiuser, repo=repo)
1110 Session().commit()
1118 Session().commit()
1111 return {
1119 return {
1112 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1120 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1113 'repository': repo.get_api_data(include_secrets=include_secrets)
1121 'repository': repo.get_api_data(include_secrets=include_secrets)
1114 }
1122 }
1115 except Exception:
1123 except Exception:
1116 log.exception(
1124 log.exception(
1117 u"Exception while trying to update the repository %s",
1125 u"Exception while trying to update the repository %s",
1118 repoid)
1126 repoid)
1119 raise JSONRPCError('failed to update repo `%s`' % repoid)
1127 raise JSONRPCError('failed to update repo `%s`' % repoid)
1120
1128
1121
1129
1122 @jsonrpc_method()
1130 @jsonrpc_method()
1123 def fork_repo(request, apiuser, repoid, fork_name,
1131 def fork_repo(request, apiuser, repoid, fork_name,
1124 owner=Optional(OAttr('apiuser')),
1132 owner=Optional(OAttr('apiuser')),
1125 description=Optional(''),
1133 description=Optional(''),
1126 private=Optional(False),
1134 private=Optional(False),
1127 clone_uri=Optional(None),
1135 clone_uri=Optional(None),
1128 landing_rev=Optional('rev:tip'),
1136 landing_rev=Optional(None),
1129 copy_permissions=Optional(False)):
1137 copy_permissions=Optional(False)):
1130 """
1138 """
1131 Creates a fork of the specified |repo|.
1139 Creates a fork of the specified |repo|.
1132
1140
1133 * If the fork_name contains "/", fork will be created inside
1141 * If the fork_name contains "/", fork will be created inside
1134 a repository group or nested repository groups
1142 a repository group or nested repository groups
1135
1143
1136 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1144 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1137 inside group "foo/bar". You have to have permissions to access and
1145 inside group "foo/bar". You have to have permissions to access and
1138 write to the last repository group ("bar" in this example)
1146 write to the last repository group ("bar" in this example)
1139
1147
1140 This command can only be run using an |authtoken| with minimum
1148 This command can only be run using an |authtoken| with minimum
1141 read permissions of the forked repo, create fork permissions for an user.
1149 read permissions of the forked repo, create fork permissions for an user.
1142
1150
1143 :param apiuser: This is filled automatically from the |authtoken|.
1151 :param apiuser: This is filled automatically from the |authtoken|.
1144 :type apiuser: AuthUser
1152 :type apiuser: AuthUser
1145 :param repoid: Set repository name or repository ID.
1153 :param repoid: Set repository name or repository ID.
1146 :type repoid: str or int
1154 :type repoid: str or int
1147 :param fork_name: Set the fork name, including it's repository group membership.
1155 :param fork_name: Set the fork name, including it's repository group membership.
1148 :type fork_name: str
1156 :type fork_name: str
1149 :param owner: Set the fork owner.
1157 :param owner: Set the fork owner.
1150 :type owner: str
1158 :type owner: str
1151 :param description: Set the fork description.
1159 :param description: Set the fork description.
1152 :type description: str
1160 :type description: str
1153 :param copy_permissions: Copy permissions from parent |repo|. The
1161 :param copy_permissions: Copy permissions from parent |repo|. The
1154 default is False.
1162 default is False.
1155 :type copy_permissions: bool
1163 :type copy_permissions: bool
1156 :param private: Make the fork private. The default is False.
1164 :param private: Make the fork private. The default is False.
1157 :type private: bool
1165 :type private: bool
1158 :param landing_rev: Set the landing revision. The default is tip.
1166 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1159
1167
1160 Example output:
1168 Example output:
1161
1169
1162 .. code-block:: bash
1170 .. code-block:: bash
1163
1171
1164 id : <id_for_response>
1172 id : <id_for_response>
1165 api_key : "<api_key>"
1173 api_key : "<api_key>"
1166 args: {
1174 args: {
1167 "repoid" : "<reponame or repo_id>",
1175 "repoid" : "<reponame or repo_id>",
1168 "fork_name": "<forkname>",
1176 "fork_name": "<forkname>",
1169 "owner": "<username or user_id = Optional(=apiuser)>",
1177 "owner": "<username or user_id = Optional(=apiuser)>",
1170 "description": "<description>",
1178 "description": "<description>",
1171 "copy_permissions": "<bool>",
1179 "copy_permissions": "<bool>",
1172 "private": "<bool>",
1180 "private": "<bool>",
1173 "landing_rev": "<landing_rev>"
1181 "landing_rev": "<landing_rev>"
1174 }
1182 }
1175
1183
1176 Example error output:
1184 Example error output:
1177
1185
1178 .. code-block:: bash
1186 .. code-block:: bash
1179
1187
1180 id : <id_given_in_input>
1188 id : <id_given_in_input>
1181 result: {
1189 result: {
1182 "msg": "Created fork of `<reponame>` as `<forkname>`",
1190 "msg": "Created fork of `<reponame>` as `<forkname>`",
1183 "success": true,
1191 "success": true,
1184 "task": "<celery task id or None if done sync>"
1192 "task": "<celery task id or None if done sync>"
1185 }
1193 }
1186 error: null
1194 error: null
1187
1195
1188 """
1196 """
1189
1197
1190 repo = get_repo_or_error(repoid)
1198 repo = get_repo_or_error(repoid)
1191 repo_name = repo.repo_name
1199 repo_name = repo.repo_name
1192
1200
1193 if not has_superadmin_permission(apiuser):
1201 if not has_superadmin_permission(apiuser):
1194 # check if we have at least read permission for
1202 # check if we have at least read permission for
1195 # this repo that we fork !
1203 # this repo that we fork !
1196 _perms = (
1204 _perms = (
1197 'repository.admin', 'repository.write', 'repository.read')
1205 'repository.admin', 'repository.write', 'repository.read')
1198 validate_repo_permissions(apiuser, repoid, repo, _perms)
1206 validate_repo_permissions(apiuser, repoid, repo, _perms)
1199
1207
1200 # check if the regular user has at least fork permissions as well
1208 # check if the regular user has at least fork permissions as well
1201 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1209 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1202 raise JSONRPCForbidden()
1210 raise JSONRPCForbidden()
1203
1211
1204 # check if user can set owner parameter
1212 # check if user can set owner parameter
1205 owner = validate_set_owner_permissions(apiuser, owner)
1213 owner = validate_set_owner_permissions(apiuser, owner)
1206
1214
1207 description = Optional.extract(description)
1215 description = Optional.extract(description)
1208 copy_permissions = Optional.extract(copy_permissions)
1216 copy_permissions = Optional.extract(copy_permissions)
1209 clone_uri = Optional.extract(clone_uri)
1217 clone_uri = Optional.extract(clone_uri)
1210 landing_commit_ref = Optional.extract(landing_rev)
1218
1219 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1220 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1221 ref_choices = list(set(ref_choices + [landing_ref]))
1222 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1223
1211 private = Optional.extract(private)
1224 private = Optional.extract(private)
1212
1225
1213 schema = repo_schema.RepoSchema().bind(
1226 schema = repo_schema.RepoSchema().bind(
1214 repo_type_options=rhodecode.BACKENDS.keys(),
1227 repo_type_options=rhodecode.BACKENDS.keys(),
1228 repo_ref_options=ref_choices,
1215 repo_type=repo.repo_type,
1229 repo_type=repo.repo_type,
1216 # user caller
1230 # user caller
1217 user=apiuser)
1231 user=apiuser)
1218
1232
1219 try:
1233 try:
1220 schema_data = schema.deserialize(dict(
1234 schema_data = schema.deserialize(dict(
1221 repo_name=fork_name,
1235 repo_name=fork_name,
1222 repo_type=repo.repo_type,
1236 repo_type=repo.repo_type,
1223 repo_owner=owner.username,
1237 repo_owner=owner.username,
1224 repo_description=description,
1238 repo_description=description,
1225 repo_landing_commit_ref=landing_commit_ref,
1239 repo_landing_commit_ref=landing_commit_ref,
1226 repo_clone_uri=clone_uri,
1240 repo_clone_uri=clone_uri,
1227 repo_private=private,
1241 repo_private=private,
1228 repo_copy_permissions=copy_permissions))
1242 repo_copy_permissions=copy_permissions))
1229 except validation_schema.Invalid as err:
1243 except validation_schema.Invalid as err:
1230 raise JSONRPCValidationError(colander_exc=err)
1244 raise JSONRPCValidationError(colander_exc=err)
1231
1245
1232 try:
1246 try:
1233 data = {
1247 data = {
1234 'fork_parent_id': repo.repo_id,
1248 'fork_parent_id': repo.repo_id,
1235
1249
1236 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1250 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1237 'repo_name_full': schema_data['repo_name'],
1251 'repo_name_full': schema_data['repo_name'],
1238 'repo_group': schema_data['repo_group']['repo_group_id'],
1252 'repo_group': schema_data['repo_group']['repo_group_id'],
1239 'repo_type': schema_data['repo_type'],
1253 'repo_type': schema_data['repo_type'],
1240 'description': schema_data['repo_description'],
1254 'description': schema_data['repo_description'],
1241 'private': schema_data['repo_private'],
1255 'private': schema_data['repo_private'],
1242 'copy_permissions': schema_data['repo_copy_permissions'],
1256 'copy_permissions': schema_data['repo_copy_permissions'],
1243 'landing_rev': schema_data['repo_landing_commit_ref'],
1257 'landing_rev': schema_data['repo_landing_commit_ref'],
1244 }
1258 }
1245
1259
1246 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1260 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1247 # no commit, it's done in RepoModel, or async via celery
1261 # no commit, it's done in RepoModel, or async via celery
1248 task_id = get_task_id(task)
1262 task_id = get_task_id(task)
1249
1263
1250 return {
1264 return {
1251 'msg': 'Created fork of `%s` as `%s`' % (
1265 'msg': 'Created fork of `%s` as `%s`' % (
1252 repo.repo_name, schema_data['repo_name']),
1266 repo.repo_name, schema_data['repo_name']),
1253 'success': True, # cannot return the repo data here since fork
1267 'success': True, # cannot return the repo data here since fork
1254 # can be done async
1268 # can be done async
1255 'task': task_id
1269 'task': task_id
1256 }
1270 }
1257 except Exception:
1271 except Exception:
1258 log.exception(
1272 log.exception(
1259 u"Exception while trying to create fork %s",
1273 u"Exception while trying to create fork %s",
1260 schema_data['repo_name'])
1274 schema_data['repo_name'])
1261 raise JSONRPCError(
1275 raise JSONRPCError(
1262 'failed to fork repository `%s` as `%s`' % (
1276 'failed to fork repository `%s` as `%s`' % (
1263 repo_name, schema_data['repo_name']))
1277 repo_name, schema_data['repo_name']))
1264
1278
1265
1279
1266 @jsonrpc_method()
1280 @jsonrpc_method()
1267 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1281 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1268 """
1282 """
1269 Deletes a repository.
1283 Deletes a repository.
1270
1284
1271 * When the `forks` parameter is set it's possible to detach or delete
1285 * When the `forks` parameter is set it's possible to detach or delete
1272 forks of deleted repository.
1286 forks of deleted repository.
1273
1287
1274 This command can only be run using an |authtoken| with admin
1288 This command can only be run using an |authtoken| with admin
1275 permissions on the |repo|.
1289 permissions on the |repo|.
1276
1290
1277 :param apiuser: This is filled automatically from the |authtoken|.
1291 :param apiuser: This is filled automatically from the |authtoken|.
1278 :type apiuser: AuthUser
1292 :type apiuser: AuthUser
1279 :param repoid: Set the repository name or repository ID.
1293 :param repoid: Set the repository name or repository ID.
1280 :type repoid: str or int
1294 :type repoid: str or int
1281 :param forks: Set to `detach` or `delete` forks from the |repo|.
1295 :param forks: Set to `detach` or `delete` forks from the |repo|.
1282 :type forks: Optional(str)
1296 :type forks: Optional(str)
1283
1297
1284 Example error output:
1298 Example error output:
1285
1299
1286 .. code-block:: bash
1300 .. code-block:: bash
1287
1301
1288 id : <id_given_in_input>
1302 id : <id_given_in_input>
1289 result: {
1303 result: {
1290 "msg": "Deleted repository `<reponame>`",
1304 "msg": "Deleted repository `<reponame>`",
1291 "success": true
1305 "success": true
1292 }
1306 }
1293 error: null
1307 error: null
1294 """
1308 """
1295
1309
1296 repo = get_repo_or_error(repoid)
1310 repo = get_repo_or_error(repoid)
1297 repo_name = repo.repo_name
1311 repo_name = repo.repo_name
1298 if not has_superadmin_permission(apiuser):
1312 if not has_superadmin_permission(apiuser):
1299 _perms = ('repository.admin',)
1313 _perms = ('repository.admin',)
1300 validate_repo_permissions(apiuser, repoid, repo, _perms)
1314 validate_repo_permissions(apiuser, repoid, repo, _perms)
1301
1315
1302 try:
1316 try:
1303 handle_forks = Optional.extract(forks)
1317 handle_forks = Optional.extract(forks)
1304 _forks_msg = ''
1318 _forks_msg = ''
1305 _forks = [f for f in repo.forks]
1319 _forks = [f for f in repo.forks]
1306 if handle_forks == 'detach':
1320 if handle_forks == 'detach':
1307 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1321 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1308 elif handle_forks == 'delete':
1322 elif handle_forks == 'delete':
1309 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1323 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1310 elif _forks:
1324 elif _forks:
1311 raise JSONRPCError(
1325 raise JSONRPCError(
1312 'Cannot delete `%s` it still contains attached forks' %
1326 'Cannot delete `%s` it still contains attached forks' %
1313 (repo.repo_name,)
1327 (repo.repo_name,)
1314 )
1328 )
1315 old_data = repo.get_api_data()
1329 old_data = repo.get_api_data()
1316 RepoModel().delete(repo, forks=forks)
1330 RepoModel().delete(repo, forks=forks)
1317
1331
1318 repo = audit_logger.RepoWrap(repo_id=None,
1332 repo = audit_logger.RepoWrap(repo_id=None,
1319 repo_name=repo.repo_name)
1333 repo_name=repo.repo_name)
1320
1334
1321 audit_logger.store_api(
1335 audit_logger.store_api(
1322 'repo.delete', action_data={'old_data': old_data},
1336 'repo.delete', action_data={'old_data': old_data},
1323 user=apiuser, repo=repo)
1337 user=apiuser, repo=repo)
1324
1338
1325 ScmModel().mark_for_invalidation(repo_name, delete=True)
1339 ScmModel().mark_for_invalidation(repo_name, delete=True)
1326 Session().commit()
1340 Session().commit()
1327 return {
1341 return {
1328 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1342 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1329 'success': True
1343 'success': True
1330 }
1344 }
1331 except Exception:
1345 except Exception:
1332 log.exception("Exception occurred while trying to delete repo")
1346 log.exception("Exception occurred while trying to delete repo")
1333 raise JSONRPCError(
1347 raise JSONRPCError(
1334 'failed to delete repository `%s`' % (repo_name,)
1348 'failed to delete repository `%s`' % (repo_name,)
1335 )
1349 )
1336
1350
1337
1351
1338 #TODO: marcink, change name ?
1352 #TODO: marcink, change name ?
1339 @jsonrpc_method()
1353 @jsonrpc_method()
1340 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1354 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1341 """
1355 """
1342 Invalidates the cache for the specified repository.
1356 Invalidates the cache for the specified repository.
1343
1357
1344 This command can only be run using an |authtoken| with admin rights to
1358 This command can only be run using an |authtoken| with admin rights to
1345 the specified repository.
1359 the specified repository.
1346
1360
1347 This command takes the following options:
1361 This command takes the following options:
1348
1362
1349 :param apiuser: This is filled automatically from |authtoken|.
1363 :param apiuser: This is filled automatically from |authtoken|.
1350 :type apiuser: AuthUser
1364 :type apiuser: AuthUser
1351 :param repoid: Sets the repository name or repository ID.
1365 :param repoid: Sets the repository name or repository ID.
1352 :type repoid: str or int
1366 :type repoid: str or int
1353 :param delete_keys: This deletes the invalidated keys instead of
1367 :param delete_keys: This deletes the invalidated keys instead of
1354 just flagging them.
1368 just flagging them.
1355 :type delete_keys: Optional(``True`` | ``False``)
1369 :type delete_keys: Optional(``True`` | ``False``)
1356
1370
1357 Example output:
1371 Example output:
1358
1372
1359 .. code-block:: bash
1373 .. code-block:: bash
1360
1374
1361 id : <id_given_in_input>
1375 id : <id_given_in_input>
1362 result : {
1376 result : {
1363 'msg': Cache for repository `<repository name>` was invalidated,
1377 'msg': Cache for repository `<repository name>` was invalidated,
1364 'repository': <repository name>
1378 'repository': <repository name>
1365 }
1379 }
1366 error : null
1380 error : null
1367
1381
1368 Example error output:
1382 Example error output:
1369
1383
1370 .. code-block:: bash
1384 .. code-block:: bash
1371
1385
1372 id : <id_given_in_input>
1386 id : <id_given_in_input>
1373 result : null
1387 result : null
1374 error : {
1388 error : {
1375 'Error occurred during cache invalidation action'
1389 'Error occurred during cache invalidation action'
1376 }
1390 }
1377
1391
1378 """
1392 """
1379
1393
1380 repo = get_repo_or_error(repoid)
1394 repo = get_repo_or_error(repoid)
1381 if not has_superadmin_permission(apiuser):
1395 if not has_superadmin_permission(apiuser):
1382 _perms = ('repository.admin', 'repository.write',)
1396 _perms = ('repository.admin', 'repository.write',)
1383 validate_repo_permissions(apiuser, repoid, repo, _perms)
1397 validate_repo_permissions(apiuser, repoid, repo, _perms)
1384
1398
1385 delete = Optional.extract(delete_keys)
1399 delete = Optional.extract(delete_keys)
1386 try:
1400 try:
1387 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1401 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1388 return {
1402 return {
1389 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1403 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1390 'repository': repo.repo_name
1404 'repository': repo.repo_name
1391 }
1405 }
1392 except Exception:
1406 except Exception:
1393 log.exception(
1407 log.exception(
1394 "Exception occurred while trying to invalidate repo cache")
1408 "Exception occurred while trying to invalidate repo cache")
1395 raise JSONRPCError(
1409 raise JSONRPCError(
1396 'Error occurred during cache invalidation action'
1410 'Error occurred during cache invalidation action'
1397 )
1411 )
1398
1412
1399
1413
1400 #TODO: marcink, change name ?
1414 #TODO: marcink, change name ?
1401 @jsonrpc_method()
1415 @jsonrpc_method()
1402 def lock(request, apiuser, repoid, locked=Optional(None),
1416 def lock(request, apiuser, repoid, locked=Optional(None),
1403 userid=Optional(OAttr('apiuser'))):
1417 userid=Optional(OAttr('apiuser'))):
1404 """
1418 """
1405 Sets the lock state of the specified |repo| by the given user.
1419 Sets the lock state of the specified |repo| by the given user.
1406 From more information, see :ref:`repo-locking`.
1420 From more information, see :ref:`repo-locking`.
1407
1421
1408 * If the ``userid`` option is not set, the repository is locked to the
1422 * If the ``userid`` option is not set, the repository is locked to the
1409 user who called the method.
1423 user who called the method.
1410 * If the ``locked`` parameter is not set, the current lock state of the
1424 * If the ``locked`` parameter is not set, the current lock state of the
1411 repository is displayed.
1425 repository is displayed.
1412
1426
1413 This command can only be run using an |authtoken| with admin rights to
1427 This command can only be run using an |authtoken| with admin rights to
1414 the specified repository.
1428 the specified repository.
1415
1429
1416 This command takes the following options:
1430 This command takes the following options:
1417
1431
1418 :param apiuser: This is filled automatically from the |authtoken|.
1432 :param apiuser: This is filled automatically from the |authtoken|.
1419 :type apiuser: AuthUser
1433 :type apiuser: AuthUser
1420 :param repoid: Sets the repository name or repository ID.
1434 :param repoid: Sets the repository name or repository ID.
1421 :type repoid: str or int
1435 :type repoid: str or int
1422 :param locked: Sets the lock state.
1436 :param locked: Sets the lock state.
1423 :type locked: Optional(``True`` | ``False``)
1437 :type locked: Optional(``True`` | ``False``)
1424 :param userid: Set the repository lock to this user.
1438 :param userid: Set the repository lock to this user.
1425 :type userid: Optional(str or int)
1439 :type userid: Optional(str or int)
1426
1440
1427 Example error output:
1441 Example error output:
1428
1442
1429 .. code-block:: bash
1443 .. code-block:: bash
1430
1444
1431 id : <id_given_in_input>
1445 id : <id_given_in_input>
1432 result : {
1446 result : {
1433 'repo': '<reponame>',
1447 'repo': '<reponame>',
1434 'locked': <bool: lock state>,
1448 'locked': <bool: lock state>,
1435 'locked_since': <int: lock timestamp>,
1449 'locked_since': <int: lock timestamp>,
1436 'locked_by': <username of person who made the lock>,
1450 'locked_by': <username of person who made the lock>,
1437 'lock_reason': <str: reason for locking>,
1451 'lock_reason': <str: reason for locking>,
1438 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1452 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1439 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1453 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1440 or
1454 or
1441 'msg': 'Repo `<repository name>` not locked.'
1455 'msg': 'Repo `<repository name>` not locked.'
1442 or
1456 or
1443 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1457 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1444 }
1458 }
1445 error : null
1459 error : null
1446
1460
1447 Example error output:
1461 Example error output:
1448
1462
1449 .. code-block:: bash
1463 .. code-block:: bash
1450
1464
1451 id : <id_given_in_input>
1465 id : <id_given_in_input>
1452 result : null
1466 result : null
1453 error : {
1467 error : {
1454 'Error occurred locking repository `<reponame>`'
1468 'Error occurred locking repository `<reponame>`'
1455 }
1469 }
1456 """
1470 """
1457
1471
1458 repo = get_repo_or_error(repoid)
1472 repo = get_repo_or_error(repoid)
1459 if not has_superadmin_permission(apiuser):
1473 if not has_superadmin_permission(apiuser):
1460 # check if we have at least write permission for this repo !
1474 # check if we have at least write permission for this repo !
1461 _perms = ('repository.admin', 'repository.write',)
1475 _perms = ('repository.admin', 'repository.write',)
1462 validate_repo_permissions(apiuser, repoid, repo, _perms)
1476 validate_repo_permissions(apiuser, repoid, repo, _perms)
1463
1477
1464 # make sure normal user does not pass someone else userid,
1478 # make sure normal user does not pass someone else userid,
1465 # he is not allowed to do that
1479 # he is not allowed to do that
1466 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1480 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1467 raise JSONRPCError('userid is not the same as your user')
1481 raise JSONRPCError('userid is not the same as your user')
1468
1482
1469 if isinstance(userid, Optional):
1483 if isinstance(userid, Optional):
1470 userid = apiuser.user_id
1484 userid = apiuser.user_id
1471
1485
1472 user = get_user_or_error(userid)
1486 user = get_user_or_error(userid)
1473
1487
1474 if isinstance(locked, Optional):
1488 if isinstance(locked, Optional):
1475 lockobj = repo.locked
1489 lockobj = repo.locked
1476
1490
1477 if lockobj[0] is None:
1491 if lockobj[0] is None:
1478 _d = {
1492 _d = {
1479 'repo': repo.repo_name,
1493 'repo': repo.repo_name,
1480 'locked': False,
1494 'locked': False,
1481 'locked_since': None,
1495 'locked_since': None,
1482 'locked_by': None,
1496 'locked_by': None,
1483 'lock_reason': None,
1497 'lock_reason': None,
1484 'lock_state_changed': False,
1498 'lock_state_changed': False,
1485 'msg': 'Repo `%s` not locked.' % repo.repo_name
1499 'msg': 'Repo `%s` not locked.' % repo.repo_name
1486 }
1500 }
1487 return _d
1501 return _d
1488 else:
1502 else:
1489 _user_id, _time, _reason = lockobj
1503 _user_id, _time, _reason = lockobj
1490 lock_user = get_user_or_error(userid)
1504 lock_user = get_user_or_error(userid)
1491 _d = {
1505 _d = {
1492 'repo': repo.repo_name,
1506 'repo': repo.repo_name,
1493 'locked': True,
1507 'locked': True,
1494 'locked_since': _time,
1508 'locked_since': _time,
1495 'locked_by': lock_user.username,
1509 'locked_by': lock_user.username,
1496 'lock_reason': _reason,
1510 'lock_reason': _reason,
1497 'lock_state_changed': False,
1511 'lock_state_changed': False,
1498 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1512 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1499 % (repo.repo_name, lock_user.username,
1513 % (repo.repo_name, lock_user.username,
1500 json.dumps(time_to_datetime(_time))))
1514 json.dumps(time_to_datetime(_time))))
1501 }
1515 }
1502 return _d
1516 return _d
1503
1517
1504 # force locked state through a flag
1518 # force locked state through a flag
1505 else:
1519 else:
1506 locked = str2bool(locked)
1520 locked = str2bool(locked)
1507 lock_reason = Repository.LOCK_API
1521 lock_reason = Repository.LOCK_API
1508 try:
1522 try:
1509 if locked:
1523 if locked:
1510 lock_time = time.time()
1524 lock_time = time.time()
1511 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1525 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1512 else:
1526 else:
1513 lock_time = None
1527 lock_time = None
1514 Repository.unlock(repo)
1528 Repository.unlock(repo)
1515 _d = {
1529 _d = {
1516 'repo': repo.repo_name,
1530 'repo': repo.repo_name,
1517 'locked': locked,
1531 'locked': locked,
1518 'locked_since': lock_time,
1532 'locked_since': lock_time,
1519 'locked_by': user.username,
1533 'locked_by': user.username,
1520 'lock_reason': lock_reason,
1534 'lock_reason': lock_reason,
1521 'lock_state_changed': True,
1535 'lock_state_changed': True,
1522 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1536 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1523 % (user.username, repo.repo_name, locked))
1537 % (user.username, repo.repo_name, locked))
1524 }
1538 }
1525 return _d
1539 return _d
1526 except Exception:
1540 except Exception:
1527 log.exception(
1541 log.exception(
1528 "Exception occurred while trying to lock repository")
1542 "Exception occurred while trying to lock repository")
1529 raise JSONRPCError(
1543 raise JSONRPCError(
1530 'Error occurred locking repository `%s`' % repo.repo_name
1544 'Error occurred locking repository `%s`' % repo.repo_name
1531 )
1545 )
1532
1546
1533
1547
1534 @jsonrpc_method()
1548 @jsonrpc_method()
1535 def comment_commit(
1549 def comment_commit(
1536 request, apiuser, repoid, commit_id, message, status=Optional(None),
1550 request, apiuser, repoid, commit_id, message, status=Optional(None),
1537 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1551 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1538 resolves_comment_id=Optional(None),
1552 resolves_comment_id=Optional(None),
1539 userid=Optional(OAttr('apiuser'))):
1553 userid=Optional(OAttr('apiuser'))):
1540 """
1554 """
1541 Set a commit comment, and optionally change the status of the commit.
1555 Set a commit comment, and optionally change the status of the commit.
1542
1556
1543 :param apiuser: This is filled automatically from the |authtoken|.
1557 :param apiuser: This is filled automatically from the |authtoken|.
1544 :type apiuser: AuthUser
1558 :type apiuser: AuthUser
1545 :param repoid: Set the repository name or repository ID.
1559 :param repoid: Set the repository name or repository ID.
1546 :type repoid: str or int
1560 :type repoid: str or int
1547 :param commit_id: Specify the commit_id for which to set a comment.
1561 :param commit_id: Specify the commit_id for which to set a comment.
1548 :type commit_id: str
1562 :type commit_id: str
1549 :param message: The comment text.
1563 :param message: The comment text.
1550 :type message: str
1564 :type message: str
1551 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1565 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1552 'approved', 'rejected', 'under_review'
1566 'approved', 'rejected', 'under_review'
1553 :type status: str
1567 :type status: str
1554 :param comment_type: Comment type, one of: 'note', 'todo'
1568 :param comment_type: Comment type, one of: 'note', 'todo'
1555 :type comment_type: Optional(str), default: 'note'
1569 :type comment_type: Optional(str), default: 'note'
1556 :param userid: Set the user name of the comment creator.
1570 :param userid: Set the user name of the comment creator.
1557 :type userid: Optional(str or int)
1571 :type userid: Optional(str or int)
1558
1572
1559 Example error output:
1573 Example error output:
1560
1574
1561 .. code-block:: bash
1575 .. code-block:: bash
1562
1576
1563 {
1577 {
1564 "id" : <id_given_in_input>,
1578 "id" : <id_given_in_input>,
1565 "result" : {
1579 "result" : {
1566 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1580 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1567 "status_change": null or <status>,
1581 "status_change": null or <status>,
1568 "success": true
1582 "success": true
1569 },
1583 },
1570 "error" : null
1584 "error" : null
1571 }
1585 }
1572
1586
1573 """
1587 """
1574 repo = get_repo_or_error(repoid)
1588 repo = get_repo_or_error(repoid)
1575 if not has_superadmin_permission(apiuser):
1589 if not has_superadmin_permission(apiuser):
1576 _perms = ('repository.read', 'repository.write', 'repository.admin')
1590 _perms = ('repository.read', 'repository.write', 'repository.admin')
1577 validate_repo_permissions(apiuser, repoid, repo, _perms)
1591 validate_repo_permissions(apiuser, repoid, repo, _perms)
1578
1592
1579 try:
1593 try:
1580 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1594 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1581 except Exception as e:
1595 except Exception as e:
1582 log.exception('Failed to fetch commit')
1596 log.exception('Failed to fetch commit')
1583 raise JSONRPCError(safe_str(e))
1597 raise JSONRPCError(safe_str(e))
1584
1598
1585 if isinstance(userid, Optional):
1599 if isinstance(userid, Optional):
1586 userid = apiuser.user_id
1600 userid = apiuser.user_id
1587
1601
1588 user = get_user_or_error(userid)
1602 user = get_user_or_error(userid)
1589 status = Optional.extract(status)
1603 status = Optional.extract(status)
1590 comment_type = Optional.extract(comment_type)
1604 comment_type = Optional.extract(comment_type)
1591 resolves_comment_id = Optional.extract(resolves_comment_id)
1605 resolves_comment_id = Optional.extract(resolves_comment_id)
1592
1606
1593 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1607 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1594 if status and status not in allowed_statuses:
1608 if status and status not in allowed_statuses:
1595 raise JSONRPCError('Bad status, must be on '
1609 raise JSONRPCError('Bad status, must be on '
1596 'of %s got %s' % (allowed_statuses, status,))
1610 'of %s got %s' % (allowed_statuses, status,))
1597
1611
1598 if resolves_comment_id:
1612 if resolves_comment_id:
1599 comment = ChangesetComment.get(resolves_comment_id)
1613 comment = ChangesetComment.get(resolves_comment_id)
1600 if not comment:
1614 if not comment:
1601 raise JSONRPCError(
1615 raise JSONRPCError(
1602 'Invalid resolves_comment_id `%s` for this commit.'
1616 'Invalid resolves_comment_id `%s` for this commit.'
1603 % resolves_comment_id)
1617 % resolves_comment_id)
1604 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1618 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1605 raise JSONRPCError(
1619 raise JSONRPCError(
1606 'Comment `%s` is wrong type for setting status to resolved.'
1620 'Comment `%s` is wrong type for setting status to resolved.'
1607 % resolves_comment_id)
1621 % resolves_comment_id)
1608
1622
1609 try:
1623 try:
1610 rc_config = SettingsModel().get_all_settings()
1624 rc_config = SettingsModel().get_all_settings()
1611 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1625 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1612 status_change_label = ChangesetStatus.get_status_lbl(status)
1626 status_change_label = ChangesetStatus.get_status_lbl(status)
1613 comment = CommentsModel().create(
1627 comment = CommentsModel().create(
1614 message, repo, user, commit_id=commit_id,
1628 message, repo, user, commit_id=commit_id,
1615 status_change=status_change_label,
1629 status_change=status_change_label,
1616 status_change_type=status,
1630 status_change_type=status,
1617 renderer=renderer,
1631 renderer=renderer,
1618 comment_type=comment_type,
1632 comment_type=comment_type,
1619 resolves_comment_id=resolves_comment_id,
1633 resolves_comment_id=resolves_comment_id,
1620 auth_user=apiuser
1634 auth_user=apiuser
1621 )
1635 )
1622 if status:
1636 if status:
1623 # also do a status change
1637 # also do a status change
1624 try:
1638 try:
1625 ChangesetStatusModel().set_status(
1639 ChangesetStatusModel().set_status(
1626 repo, status, user, comment, revision=commit_id,
1640 repo, status, user, comment, revision=commit_id,
1627 dont_allow_on_closed_pull_request=True
1641 dont_allow_on_closed_pull_request=True
1628 )
1642 )
1629 except StatusChangeOnClosedPullRequestError:
1643 except StatusChangeOnClosedPullRequestError:
1630 log.exception(
1644 log.exception(
1631 "Exception occurred while trying to change repo commit status")
1645 "Exception occurred while trying to change repo commit status")
1632 msg = ('Changing status on a changeset associated with '
1646 msg = ('Changing status on a changeset associated with '
1633 'a closed pull request is not allowed')
1647 'a closed pull request is not allowed')
1634 raise JSONRPCError(msg)
1648 raise JSONRPCError(msg)
1635
1649
1636 Session().commit()
1650 Session().commit()
1637 return {
1651 return {
1638 'msg': (
1652 'msg': (
1639 'Commented on commit `%s` for repository `%s`' % (
1653 'Commented on commit `%s` for repository `%s`' % (
1640 comment.revision, repo.repo_name)),
1654 comment.revision, repo.repo_name)),
1641 'status_change': status,
1655 'status_change': status,
1642 'success': True,
1656 'success': True,
1643 }
1657 }
1644 except JSONRPCError:
1658 except JSONRPCError:
1645 # catch any inside errors, and re-raise them to prevent from
1659 # catch any inside errors, and re-raise them to prevent from
1646 # below global catch to silence them
1660 # below global catch to silence them
1647 raise
1661 raise
1648 except Exception:
1662 except Exception:
1649 log.exception("Exception occurred while trying to comment on commit")
1663 log.exception("Exception occurred while trying to comment on commit")
1650 raise JSONRPCError(
1664 raise JSONRPCError(
1651 'failed to set comment on repository `%s`' % (repo.repo_name,)
1665 'failed to set comment on repository `%s`' % (repo.repo_name,)
1652 )
1666 )
1653
1667
1654
1668
1655 @jsonrpc_method()
1669 @jsonrpc_method()
1656 def get_repo_comments(request, apiuser, repoid,
1670 def get_repo_comments(request, apiuser, repoid,
1657 commit_id=Optional(None), comment_type=Optional(None),
1671 commit_id=Optional(None), comment_type=Optional(None),
1658 userid=Optional(None)):
1672 userid=Optional(None)):
1659 """
1673 """
1660 Get all comments for a repository
1674 Get all comments for a repository
1661
1675
1662 :param apiuser: This is filled automatically from the |authtoken|.
1676 :param apiuser: This is filled automatically from the |authtoken|.
1663 :type apiuser: AuthUser
1677 :type apiuser: AuthUser
1664 :param repoid: Set the repository name or repository ID.
1678 :param repoid: Set the repository name or repository ID.
1665 :type repoid: str or int
1679 :type repoid: str or int
1666 :param commit_id: Optionally filter the comments by the commit_id
1680 :param commit_id: Optionally filter the comments by the commit_id
1667 :type commit_id: Optional(str), default: None
1681 :type commit_id: Optional(str), default: None
1668 :param comment_type: Optionally filter the comments by the comment_type
1682 :param comment_type: Optionally filter the comments by the comment_type
1669 one of: 'note', 'todo'
1683 one of: 'note', 'todo'
1670 :type comment_type: Optional(str), default: None
1684 :type comment_type: Optional(str), default: None
1671 :param userid: Optionally filter the comments by the author of comment
1685 :param userid: Optionally filter the comments by the author of comment
1672 :type userid: Optional(str or int), Default: None
1686 :type userid: Optional(str or int), Default: None
1673
1687
1674 Example error output:
1688 Example error output:
1675
1689
1676 .. code-block:: bash
1690 .. code-block:: bash
1677
1691
1678 {
1692 {
1679 "id" : <id_given_in_input>,
1693 "id" : <id_given_in_input>,
1680 "result" : [
1694 "result" : [
1681 {
1695 {
1682 "comment_author": <USER_DETAILS>,
1696 "comment_author": <USER_DETAILS>,
1683 "comment_created_on": "2017-02-01T14:38:16.309",
1697 "comment_created_on": "2017-02-01T14:38:16.309",
1684 "comment_f_path": "file.txt",
1698 "comment_f_path": "file.txt",
1685 "comment_id": 282,
1699 "comment_id": 282,
1686 "comment_lineno": "n1",
1700 "comment_lineno": "n1",
1687 "comment_resolved_by": null,
1701 "comment_resolved_by": null,
1688 "comment_status": [],
1702 "comment_status": [],
1689 "comment_text": "This file needs a header",
1703 "comment_text": "This file needs a header",
1690 "comment_type": "todo"
1704 "comment_type": "todo"
1691 }
1705 }
1692 ],
1706 ],
1693 "error" : null
1707 "error" : null
1694 }
1708 }
1695
1709
1696 """
1710 """
1697 repo = get_repo_or_error(repoid)
1711 repo = get_repo_or_error(repoid)
1698 if not has_superadmin_permission(apiuser):
1712 if not has_superadmin_permission(apiuser):
1699 _perms = ('repository.read', 'repository.write', 'repository.admin')
1713 _perms = ('repository.read', 'repository.write', 'repository.admin')
1700 validate_repo_permissions(apiuser, repoid, repo, _perms)
1714 validate_repo_permissions(apiuser, repoid, repo, _perms)
1701
1715
1702 commit_id = Optional.extract(commit_id)
1716 commit_id = Optional.extract(commit_id)
1703
1717
1704 userid = Optional.extract(userid)
1718 userid = Optional.extract(userid)
1705 if userid:
1719 if userid:
1706 user = get_user_or_error(userid)
1720 user = get_user_or_error(userid)
1707 else:
1721 else:
1708 user = None
1722 user = None
1709
1723
1710 comment_type = Optional.extract(comment_type)
1724 comment_type = Optional.extract(comment_type)
1711 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1725 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1712 raise JSONRPCError(
1726 raise JSONRPCError(
1713 'comment_type must be one of `{}` got {}'.format(
1727 'comment_type must be one of `{}` got {}'.format(
1714 ChangesetComment.COMMENT_TYPES, comment_type)
1728 ChangesetComment.COMMENT_TYPES, comment_type)
1715 )
1729 )
1716
1730
1717 comments = CommentsModel().get_repository_comments(
1731 comments = CommentsModel().get_repository_comments(
1718 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1732 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1719 return comments
1733 return comments
1720
1734
1721
1735
1722 @jsonrpc_method()
1736 @jsonrpc_method()
1723 def grant_user_permission(request, apiuser, repoid, userid, perm):
1737 def grant_user_permission(request, apiuser, repoid, userid, perm):
1724 """
1738 """
1725 Grant permissions for the specified user on the given repository,
1739 Grant permissions for the specified user on the given repository,
1726 or update existing permissions if found.
1740 or update existing permissions if found.
1727
1741
1728 This command can only be run using an |authtoken| with admin
1742 This command can only be run using an |authtoken| with admin
1729 permissions on the |repo|.
1743 permissions on the |repo|.
1730
1744
1731 :param apiuser: This is filled automatically from the |authtoken|.
1745 :param apiuser: This is filled automatically from the |authtoken|.
1732 :type apiuser: AuthUser
1746 :type apiuser: AuthUser
1733 :param repoid: Set the repository name or repository ID.
1747 :param repoid: Set the repository name or repository ID.
1734 :type repoid: str or int
1748 :type repoid: str or int
1735 :param userid: Set the user name.
1749 :param userid: Set the user name.
1736 :type userid: str
1750 :type userid: str
1737 :param perm: Set the user permissions, using the following format
1751 :param perm: Set the user permissions, using the following format
1738 ``(repository.(none|read|write|admin))``
1752 ``(repository.(none|read|write|admin))``
1739 :type perm: str
1753 :type perm: str
1740
1754
1741 Example output:
1755 Example output:
1742
1756
1743 .. code-block:: bash
1757 .. code-block:: bash
1744
1758
1745 id : <id_given_in_input>
1759 id : <id_given_in_input>
1746 result: {
1760 result: {
1747 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1761 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1748 "success": true
1762 "success": true
1749 }
1763 }
1750 error: null
1764 error: null
1751 """
1765 """
1752
1766
1753 repo = get_repo_or_error(repoid)
1767 repo = get_repo_or_error(repoid)
1754 user = get_user_or_error(userid)
1768 user = get_user_or_error(userid)
1755 perm = get_perm_or_error(perm)
1769 perm = get_perm_or_error(perm)
1756 if not has_superadmin_permission(apiuser):
1770 if not has_superadmin_permission(apiuser):
1757 _perms = ('repository.admin',)
1771 _perms = ('repository.admin',)
1758 validate_repo_permissions(apiuser, repoid, repo, _perms)
1772 validate_repo_permissions(apiuser, repoid, repo, _perms)
1759
1773
1760 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1774 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1761 try:
1775 try:
1762 changes = RepoModel().update_permissions(
1776 changes = RepoModel().update_permissions(
1763 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1777 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1764
1778
1765 action_data = {
1779 action_data = {
1766 'added': changes['added'],
1780 'added': changes['added'],
1767 'updated': changes['updated'],
1781 'updated': changes['updated'],
1768 'deleted': changes['deleted'],
1782 'deleted': changes['deleted'],
1769 }
1783 }
1770 audit_logger.store_api(
1784 audit_logger.store_api(
1771 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1785 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1772
1786
1773 Session().commit()
1787 Session().commit()
1774 return {
1788 return {
1775 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1789 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1776 perm.permission_name, user.username, repo.repo_name
1790 perm.permission_name, user.username, repo.repo_name
1777 ),
1791 ),
1778 'success': True
1792 'success': True
1779 }
1793 }
1780 except Exception:
1794 except Exception:
1781 log.exception("Exception occurred while trying edit permissions for repo")
1795 log.exception("Exception occurred while trying edit permissions for repo")
1782 raise JSONRPCError(
1796 raise JSONRPCError(
1783 'failed to edit permission for user: `%s` in repo: `%s`' % (
1797 'failed to edit permission for user: `%s` in repo: `%s`' % (
1784 userid, repoid
1798 userid, repoid
1785 )
1799 )
1786 )
1800 )
1787
1801
1788
1802
1789 @jsonrpc_method()
1803 @jsonrpc_method()
1790 def revoke_user_permission(request, apiuser, repoid, userid):
1804 def revoke_user_permission(request, apiuser, repoid, userid):
1791 """
1805 """
1792 Revoke permission for a user on the specified repository.
1806 Revoke permission for a user on the specified repository.
1793
1807
1794 This command can only be run using an |authtoken| with admin
1808 This command can only be run using an |authtoken| with admin
1795 permissions on the |repo|.
1809 permissions on the |repo|.
1796
1810
1797 :param apiuser: This is filled automatically from the |authtoken|.
1811 :param apiuser: This is filled automatically from the |authtoken|.
1798 :type apiuser: AuthUser
1812 :type apiuser: AuthUser
1799 :param repoid: Set the repository name or repository ID.
1813 :param repoid: Set the repository name or repository ID.
1800 :type repoid: str or int
1814 :type repoid: str or int
1801 :param userid: Set the user name of revoked user.
1815 :param userid: Set the user name of revoked user.
1802 :type userid: str or int
1816 :type userid: str or int
1803
1817
1804 Example error output:
1818 Example error output:
1805
1819
1806 .. code-block:: bash
1820 .. code-block:: bash
1807
1821
1808 id : <id_given_in_input>
1822 id : <id_given_in_input>
1809 result: {
1823 result: {
1810 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1824 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1811 "success": true
1825 "success": true
1812 }
1826 }
1813 error: null
1827 error: null
1814 """
1828 """
1815
1829
1816 repo = get_repo_or_error(repoid)
1830 repo = get_repo_or_error(repoid)
1817 user = get_user_or_error(userid)
1831 user = get_user_or_error(userid)
1818 if not has_superadmin_permission(apiuser):
1832 if not has_superadmin_permission(apiuser):
1819 _perms = ('repository.admin',)
1833 _perms = ('repository.admin',)
1820 validate_repo_permissions(apiuser, repoid, repo, _perms)
1834 validate_repo_permissions(apiuser, repoid, repo, _perms)
1821
1835
1822 perm_deletions = [[user.user_id, None, "user"]]
1836 perm_deletions = [[user.user_id, None, "user"]]
1823 try:
1837 try:
1824 changes = RepoModel().update_permissions(
1838 changes = RepoModel().update_permissions(
1825 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1839 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1826
1840
1827 action_data = {
1841 action_data = {
1828 'added': changes['added'],
1842 'added': changes['added'],
1829 'updated': changes['updated'],
1843 'updated': changes['updated'],
1830 'deleted': changes['deleted'],
1844 'deleted': changes['deleted'],
1831 }
1845 }
1832 audit_logger.store_api(
1846 audit_logger.store_api(
1833 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1847 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1834
1848
1835 Session().commit()
1849 Session().commit()
1836 return {
1850 return {
1837 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1851 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1838 user.username, repo.repo_name
1852 user.username, repo.repo_name
1839 ),
1853 ),
1840 'success': True
1854 'success': True
1841 }
1855 }
1842 except Exception:
1856 except Exception:
1843 log.exception("Exception occurred while trying revoke permissions to repo")
1857 log.exception("Exception occurred while trying revoke permissions to repo")
1844 raise JSONRPCError(
1858 raise JSONRPCError(
1845 'failed to edit permission for user: `%s` in repo: `%s`' % (
1859 'failed to edit permission for user: `%s` in repo: `%s`' % (
1846 userid, repoid
1860 userid, repoid
1847 )
1861 )
1848 )
1862 )
1849
1863
1850
1864
1851 @jsonrpc_method()
1865 @jsonrpc_method()
1852 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1866 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1853 """
1867 """
1854 Grant permission for a user group on the specified repository,
1868 Grant permission for a user group on the specified repository,
1855 or update existing permissions.
1869 or update existing permissions.
1856
1870
1857 This command can only be run using an |authtoken| with admin
1871 This command can only be run using an |authtoken| with admin
1858 permissions on the |repo|.
1872 permissions on the |repo|.
1859
1873
1860 :param apiuser: This is filled automatically from the |authtoken|.
1874 :param apiuser: This is filled automatically from the |authtoken|.
1861 :type apiuser: AuthUser
1875 :type apiuser: AuthUser
1862 :param repoid: Set the repository name or repository ID.
1876 :param repoid: Set the repository name or repository ID.
1863 :type repoid: str or int
1877 :type repoid: str or int
1864 :param usergroupid: Specify the ID of the user group.
1878 :param usergroupid: Specify the ID of the user group.
1865 :type usergroupid: str or int
1879 :type usergroupid: str or int
1866 :param perm: Set the user group permissions using the following
1880 :param perm: Set the user group permissions using the following
1867 format: (repository.(none|read|write|admin))
1881 format: (repository.(none|read|write|admin))
1868 :type perm: str
1882 :type perm: str
1869
1883
1870 Example output:
1884 Example output:
1871
1885
1872 .. code-block:: bash
1886 .. code-block:: bash
1873
1887
1874 id : <id_given_in_input>
1888 id : <id_given_in_input>
1875 result : {
1889 result : {
1876 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1890 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1877 "success": true
1891 "success": true
1878
1892
1879 }
1893 }
1880 error : null
1894 error : null
1881
1895
1882 Example error output:
1896 Example error output:
1883
1897
1884 .. code-block:: bash
1898 .. code-block:: bash
1885
1899
1886 id : <id_given_in_input>
1900 id : <id_given_in_input>
1887 result : null
1901 result : null
1888 error : {
1902 error : {
1889 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1903 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1890 }
1904 }
1891
1905
1892 """
1906 """
1893
1907
1894 repo = get_repo_or_error(repoid)
1908 repo = get_repo_or_error(repoid)
1895 perm = get_perm_or_error(perm)
1909 perm = get_perm_or_error(perm)
1896 if not has_superadmin_permission(apiuser):
1910 if not has_superadmin_permission(apiuser):
1897 _perms = ('repository.admin',)
1911 _perms = ('repository.admin',)
1898 validate_repo_permissions(apiuser, repoid, repo, _perms)
1912 validate_repo_permissions(apiuser, repoid, repo, _perms)
1899
1913
1900 user_group = get_user_group_or_error(usergroupid)
1914 user_group = get_user_group_or_error(usergroupid)
1901 if not has_superadmin_permission(apiuser):
1915 if not has_superadmin_permission(apiuser):
1902 # check if we have at least read permission for this user group !
1916 # check if we have at least read permission for this user group !
1903 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1917 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1904 if not HasUserGroupPermissionAnyApi(*_perms)(
1918 if not HasUserGroupPermissionAnyApi(*_perms)(
1905 user=apiuser, user_group_name=user_group.users_group_name):
1919 user=apiuser, user_group_name=user_group.users_group_name):
1906 raise JSONRPCError(
1920 raise JSONRPCError(
1907 'user group `%s` does not exist' % (usergroupid,))
1921 'user group `%s` does not exist' % (usergroupid,))
1908
1922
1909 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1923 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1910 try:
1924 try:
1911 changes = RepoModel().update_permissions(
1925 changes = RepoModel().update_permissions(
1912 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1926 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1913 action_data = {
1927 action_data = {
1914 'added': changes['added'],
1928 'added': changes['added'],
1915 'updated': changes['updated'],
1929 'updated': changes['updated'],
1916 'deleted': changes['deleted'],
1930 'deleted': changes['deleted'],
1917 }
1931 }
1918 audit_logger.store_api(
1932 audit_logger.store_api(
1919 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1933 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1920
1934
1921 Session().commit()
1935 Session().commit()
1922 return {
1936 return {
1923 'msg': 'Granted perm: `%s` for user group: `%s` in '
1937 'msg': 'Granted perm: `%s` for user group: `%s` in '
1924 'repo: `%s`' % (
1938 'repo: `%s`' % (
1925 perm.permission_name, user_group.users_group_name,
1939 perm.permission_name, user_group.users_group_name,
1926 repo.repo_name
1940 repo.repo_name
1927 ),
1941 ),
1928 'success': True
1942 'success': True
1929 }
1943 }
1930 except Exception:
1944 except Exception:
1931 log.exception(
1945 log.exception(
1932 "Exception occurred while trying change permission on repo")
1946 "Exception occurred while trying change permission on repo")
1933 raise JSONRPCError(
1947 raise JSONRPCError(
1934 'failed to edit permission for user group: `%s` in '
1948 'failed to edit permission for user group: `%s` in '
1935 'repo: `%s`' % (
1949 'repo: `%s`' % (
1936 usergroupid, repo.repo_name
1950 usergroupid, repo.repo_name
1937 )
1951 )
1938 )
1952 )
1939
1953
1940
1954
1941 @jsonrpc_method()
1955 @jsonrpc_method()
1942 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1956 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1943 """
1957 """
1944 Revoke the permissions of a user group on a given repository.
1958 Revoke the permissions of a user group on a given repository.
1945
1959
1946 This command can only be run using an |authtoken| with admin
1960 This command can only be run using an |authtoken| with admin
1947 permissions on the |repo|.
1961 permissions on the |repo|.
1948
1962
1949 :param apiuser: This is filled automatically from the |authtoken|.
1963 :param apiuser: This is filled automatically from the |authtoken|.
1950 :type apiuser: AuthUser
1964 :type apiuser: AuthUser
1951 :param repoid: Set the repository name or repository ID.
1965 :param repoid: Set the repository name or repository ID.
1952 :type repoid: str or int
1966 :type repoid: str or int
1953 :param usergroupid: Specify the user group ID.
1967 :param usergroupid: Specify the user group ID.
1954 :type usergroupid: str or int
1968 :type usergroupid: str or int
1955
1969
1956 Example output:
1970 Example output:
1957
1971
1958 .. code-block:: bash
1972 .. code-block:: bash
1959
1973
1960 id : <id_given_in_input>
1974 id : <id_given_in_input>
1961 result: {
1975 result: {
1962 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1976 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1963 "success": true
1977 "success": true
1964 }
1978 }
1965 error: null
1979 error: null
1966 """
1980 """
1967
1981
1968 repo = get_repo_or_error(repoid)
1982 repo = get_repo_or_error(repoid)
1969 if not has_superadmin_permission(apiuser):
1983 if not has_superadmin_permission(apiuser):
1970 _perms = ('repository.admin',)
1984 _perms = ('repository.admin',)
1971 validate_repo_permissions(apiuser, repoid, repo, _perms)
1985 validate_repo_permissions(apiuser, repoid, repo, _perms)
1972
1986
1973 user_group = get_user_group_or_error(usergroupid)
1987 user_group = get_user_group_or_error(usergroupid)
1974 if not has_superadmin_permission(apiuser):
1988 if not has_superadmin_permission(apiuser):
1975 # check if we have at least read permission for this user group !
1989 # check if we have at least read permission for this user group !
1976 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1990 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1977 if not HasUserGroupPermissionAnyApi(*_perms)(
1991 if not HasUserGroupPermissionAnyApi(*_perms)(
1978 user=apiuser, user_group_name=user_group.users_group_name):
1992 user=apiuser, user_group_name=user_group.users_group_name):
1979 raise JSONRPCError(
1993 raise JSONRPCError(
1980 'user group `%s` does not exist' % (usergroupid,))
1994 'user group `%s` does not exist' % (usergroupid,))
1981
1995
1982 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
1996 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
1983 try:
1997 try:
1984 changes = RepoModel().update_permissions(
1998 changes = RepoModel().update_permissions(
1985 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
1999 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
1986 action_data = {
2000 action_data = {
1987 'added': changes['added'],
2001 'added': changes['added'],
1988 'updated': changes['updated'],
2002 'updated': changes['updated'],
1989 'deleted': changes['deleted'],
2003 'deleted': changes['deleted'],
1990 }
2004 }
1991 audit_logger.store_api(
2005 audit_logger.store_api(
1992 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2006 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1993
2007
1994 Session().commit()
2008 Session().commit()
1995 return {
2009 return {
1996 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2010 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1997 user_group.users_group_name, repo.repo_name
2011 user_group.users_group_name, repo.repo_name
1998 ),
2012 ),
1999 'success': True
2013 'success': True
2000 }
2014 }
2001 except Exception:
2015 except Exception:
2002 log.exception("Exception occurred while trying revoke "
2016 log.exception("Exception occurred while trying revoke "
2003 "user group permission on repo")
2017 "user group permission on repo")
2004 raise JSONRPCError(
2018 raise JSONRPCError(
2005 'failed to edit permission for user group: `%s` in '
2019 'failed to edit permission for user group: `%s` in '
2006 'repo: `%s`' % (
2020 'repo: `%s`' % (
2007 user_group.users_group_name, repo.repo_name
2021 user_group.users_group_name, repo.repo_name
2008 )
2022 )
2009 )
2023 )
2010
2024
2011
2025
2012 @jsonrpc_method()
2026 @jsonrpc_method()
2013 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2027 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2014 """
2028 """
2015 Triggers a pull on the given repository from a remote location. You
2029 Triggers a pull on the given repository from a remote location. You
2016 can use this to keep remote repositories up-to-date.
2030 can use this to keep remote repositories up-to-date.
2017
2031
2018 This command can only be run using an |authtoken| with admin
2032 This command can only be run using an |authtoken| with admin
2019 rights to the specified repository. For more information,
2033 rights to the specified repository. For more information,
2020 see :ref:`config-token-ref`.
2034 see :ref:`config-token-ref`.
2021
2035
2022 This command takes the following options:
2036 This command takes the following options:
2023
2037
2024 :param apiuser: This is filled automatically from the |authtoken|.
2038 :param apiuser: This is filled automatically from the |authtoken|.
2025 :type apiuser: AuthUser
2039 :type apiuser: AuthUser
2026 :param repoid: The repository name or repository ID.
2040 :param repoid: The repository name or repository ID.
2027 :type repoid: str or int
2041 :type repoid: str or int
2028 :param remote_uri: Optional remote URI to pass in for pull
2042 :param remote_uri: Optional remote URI to pass in for pull
2029 :type remote_uri: str
2043 :type remote_uri: str
2030
2044
2031 Example output:
2045 Example output:
2032
2046
2033 .. code-block:: bash
2047 .. code-block:: bash
2034
2048
2035 id : <id_given_in_input>
2049 id : <id_given_in_input>
2036 result : {
2050 result : {
2037 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2051 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2038 "repository": "<repository name>"
2052 "repository": "<repository name>"
2039 }
2053 }
2040 error : null
2054 error : null
2041
2055
2042 Example error output:
2056 Example error output:
2043
2057
2044 .. code-block:: bash
2058 .. code-block:: bash
2045
2059
2046 id : <id_given_in_input>
2060 id : <id_given_in_input>
2047 result : null
2061 result : null
2048 error : {
2062 error : {
2049 "Unable to push changes from `<remote_url>`"
2063 "Unable to push changes from `<remote_url>`"
2050 }
2064 }
2051
2065
2052 """
2066 """
2053
2067
2054 repo = get_repo_or_error(repoid)
2068 repo = get_repo_or_error(repoid)
2055 remote_uri = Optional.extract(remote_uri)
2069 remote_uri = Optional.extract(remote_uri)
2056 remote_uri_display = remote_uri or repo.clone_uri_hidden
2070 remote_uri_display = remote_uri or repo.clone_uri_hidden
2057 if not has_superadmin_permission(apiuser):
2071 if not has_superadmin_permission(apiuser):
2058 _perms = ('repository.admin',)
2072 _perms = ('repository.admin',)
2059 validate_repo_permissions(apiuser, repoid, repo, _perms)
2073 validate_repo_permissions(apiuser, repoid, repo, _perms)
2060
2074
2061 try:
2075 try:
2062 ScmModel().pull_changes(
2076 ScmModel().pull_changes(
2063 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2077 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2064 return {
2078 return {
2065 'msg': 'Pulled from url `%s` on repo `%s`' % (
2079 'msg': 'Pulled from url `%s` on repo `%s`' % (
2066 remote_uri_display, repo.repo_name),
2080 remote_uri_display, repo.repo_name),
2067 'repository': repo.repo_name
2081 'repository': repo.repo_name
2068 }
2082 }
2069 except Exception:
2083 except Exception:
2070 log.exception("Exception occurred while trying to "
2084 log.exception("Exception occurred while trying to "
2071 "pull changes from remote location")
2085 "pull changes from remote location")
2072 raise JSONRPCError(
2086 raise JSONRPCError(
2073 'Unable to pull changes from `%s`' % remote_uri_display
2087 'Unable to pull changes from `%s`' % remote_uri_display
2074 )
2088 )
2075
2089
2076
2090
2077 @jsonrpc_method()
2091 @jsonrpc_method()
2078 def strip(request, apiuser, repoid, revision, branch):
2092 def strip(request, apiuser, repoid, revision, branch):
2079 """
2093 """
2080 Strips the given revision from the specified repository.
2094 Strips the given revision from the specified repository.
2081
2095
2082 * This will remove the revision and all of its decendants.
2096 * This will remove the revision and all of its decendants.
2083
2097
2084 This command can only be run using an |authtoken| with admin rights to
2098 This command can only be run using an |authtoken| with admin rights to
2085 the specified repository.
2099 the specified repository.
2086
2100
2087 This command takes the following options:
2101 This command takes the following options:
2088
2102
2089 :param apiuser: This is filled automatically from the |authtoken|.
2103 :param apiuser: This is filled automatically from the |authtoken|.
2090 :type apiuser: AuthUser
2104 :type apiuser: AuthUser
2091 :param repoid: The repository name or repository ID.
2105 :param repoid: The repository name or repository ID.
2092 :type repoid: str or int
2106 :type repoid: str or int
2093 :param revision: The revision you wish to strip.
2107 :param revision: The revision you wish to strip.
2094 :type revision: str
2108 :type revision: str
2095 :param branch: The branch from which to strip the revision.
2109 :param branch: The branch from which to strip the revision.
2096 :type branch: str
2110 :type branch: str
2097
2111
2098 Example output:
2112 Example output:
2099
2113
2100 .. code-block:: bash
2114 .. code-block:: bash
2101
2115
2102 id : <id_given_in_input>
2116 id : <id_given_in_input>
2103 result : {
2117 result : {
2104 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2118 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2105 "repository": "<repository name>"
2119 "repository": "<repository name>"
2106 }
2120 }
2107 error : null
2121 error : null
2108
2122
2109 Example error output:
2123 Example error output:
2110
2124
2111 .. code-block:: bash
2125 .. code-block:: bash
2112
2126
2113 id : <id_given_in_input>
2127 id : <id_given_in_input>
2114 result : null
2128 result : null
2115 error : {
2129 error : {
2116 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2130 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2117 }
2131 }
2118
2132
2119 """
2133 """
2120
2134
2121 repo = get_repo_or_error(repoid)
2135 repo = get_repo_or_error(repoid)
2122 if not has_superadmin_permission(apiuser):
2136 if not has_superadmin_permission(apiuser):
2123 _perms = ('repository.admin',)
2137 _perms = ('repository.admin',)
2124 validate_repo_permissions(apiuser, repoid, repo, _perms)
2138 validate_repo_permissions(apiuser, repoid, repo, _perms)
2125
2139
2126 try:
2140 try:
2127 ScmModel().strip(repo, revision, branch)
2141 ScmModel().strip(repo, revision, branch)
2128 audit_logger.store_api(
2142 audit_logger.store_api(
2129 'repo.commit.strip', action_data={'commit_id': revision},
2143 'repo.commit.strip', action_data={'commit_id': revision},
2130 repo=repo,
2144 repo=repo,
2131 user=apiuser, commit=True)
2145 user=apiuser, commit=True)
2132
2146
2133 return {
2147 return {
2134 'msg': 'Stripped commit %s from repo `%s`' % (
2148 'msg': 'Stripped commit %s from repo `%s`' % (
2135 revision, repo.repo_name),
2149 revision, repo.repo_name),
2136 'repository': repo.repo_name
2150 'repository': repo.repo_name
2137 }
2151 }
2138 except Exception:
2152 except Exception:
2139 log.exception("Exception while trying to strip")
2153 log.exception("Exception while trying to strip")
2140 raise JSONRPCError(
2154 raise JSONRPCError(
2141 'Unable to strip commit %s from repo `%s`' % (
2155 'Unable to strip commit %s from repo `%s`' % (
2142 revision, repo.repo_name)
2156 revision, repo.repo_name)
2143 )
2157 )
2144
2158
2145
2159
2146 @jsonrpc_method()
2160 @jsonrpc_method()
2147 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2161 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2148 """
2162 """
2149 Returns all settings for a repository. If key is given it only returns the
2163 Returns all settings for a repository. If key is given it only returns the
2150 setting identified by the key or null.
2164 setting identified by the key or null.
2151
2165
2152 :param apiuser: This is filled automatically from the |authtoken|.
2166 :param apiuser: This is filled automatically from the |authtoken|.
2153 :type apiuser: AuthUser
2167 :type apiuser: AuthUser
2154 :param repoid: The repository name or repository id.
2168 :param repoid: The repository name or repository id.
2155 :type repoid: str or int
2169 :type repoid: str or int
2156 :param key: Key of the setting to return.
2170 :param key: Key of the setting to return.
2157 :type: key: Optional(str)
2171 :type: key: Optional(str)
2158
2172
2159 Example output:
2173 Example output:
2160
2174
2161 .. code-block:: bash
2175 .. code-block:: bash
2162
2176
2163 {
2177 {
2164 "error": null,
2178 "error": null,
2165 "id": 237,
2179 "id": 237,
2166 "result": {
2180 "result": {
2167 "extensions_largefiles": true,
2181 "extensions_largefiles": true,
2168 "extensions_evolve": true,
2182 "extensions_evolve": true,
2169 "hooks_changegroup_push_logger": true,
2183 "hooks_changegroup_push_logger": true,
2170 "hooks_changegroup_repo_size": false,
2184 "hooks_changegroup_repo_size": false,
2171 "hooks_outgoing_pull_logger": true,
2185 "hooks_outgoing_pull_logger": true,
2172 "phases_publish": "True",
2186 "phases_publish": "True",
2173 "rhodecode_hg_use_rebase_for_merging": true,
2187 "rhodecode_hg_use_rebase_for_merging": true,
2174 "rhodecode_pr_merge_enabled": true,
2188 "rhodecode_pr_merge_enabled": true,
2175 "rhodecode_use_outdated_comments": true
2189 "rhodecode_use_outdated_comments": true
2176 }
2190 }
2177 }
2191 }
2178 """
2192 """
2179
2193
2180 # Restrict access to this api method to admins only.
2194 # Restrict access to this api method to admins only.
2181 if not has_superadmin_permission(apiuser):
2195 if not has_superadmin_permission(apiuser):
2182 raise JSONRPCForbidden()
2196 raise JSONRPCForbidden()
2183
2197
2184 try:
2198 try:
2185 repo = get_repo_or_error(repoid)
2199 repo = get_repo_or_error(repoid)
2186 settings_model = VcsSettingsModel(repo=repo)
2200 settings_model = VcsSettingsModel(repo=repo)
2187 settings = settings_model.get_global_settings()
2201 settings = settings_model.get_global_settings()
2188 settings.update(settings_model.get_repo_settings())
2202 settings.update(settings_model.get_repo_settings())
2189
2203
2190 # If only a single setting is requested fetch it from all settings.
2204 # If only a single setting is requested fetch it from all settings.
2191 key = Optional.extract(key)
2205 key = Optional.extract(key)
2192 if key is not None:
2206 if key is not None:
2193 settings = settings.get(key, None)
2207 settings = settings.get(key, None)
2194 except Exception:
2208 except Exception:
2195 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2209 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2196 log.exception(msg)
2210 log.exception(msg)
2197 raise JSONRPCError(msg)
2211 raise JSONRPCError(msg)
2198
2212
2199 return settings
2213 return settings
2200
2214
2201
2215
2202 @jsonrpc_method()
2216 @jsonrpc_method()
2203 def set_repo_settings(request, apiuser, repoid, settings):
2217 def set_repo_settings(request, apiuser, repoid, settings):
2204 """
2218 """
2205 Update repository settings. Returns true on success.
2219 Update repository settings. Returns true on success.
2206
2220
2207 :param apiuser: This is filled automatically from the |authtoken|.
2221 :param apiuser: This is filled automatically from the |authtoken|.
2208 :type apiuser: AuthUser
2222 :type apiuser: AuthUser
2209 :param repoid: The repository name or repository id.
2223 :param repoid: The repository name or repository id.
2210 :type repoid: str or int
2224 :type repoid: str or int
2211 :param settings: The new settings for the repository.
2225 :param settings: The new settings for the repository.
2212 :type: settings: dict
2226 :type: settings: dict
2213
2227
2214 Example output:
2228 Example output:
2215
2229
2216 .. code-block:: bash
2230 .. code-block:: bash
2217
2231
2218 {
2232 {
2219 "error": null,
2233 "error": null,
2220 "id": 237,
2234 "id": 237,
2221 "result": true
2235 "result": true
2222 }
2236 }
2223 """
2237 """
2224 # Restrict access to this api method to admins only.
2238 # Restrict access to this api method to admins only.
2225 if not has_superadmin_permission(apiuser):
2239 if not has_superadmin_permission(apiuser):
2226 raise JSONRPCForbidden()
2240 raise JSONRPCForbidden()
2227
2241
2228 if type(settings) is not dict:
2242 if type(settings) is not dict:
2229 raise JSONRPCError('Settings have to be a JSON Object.')
2243 raise JSONRPCError('Settings have to be a JSON Object.')
2230
2244
2231 try:
2245 try:
2232 settings_model = VcsSettingsModel(repo=repoid)
2246 settings_model = VcsSettingsModel(repo=repoid)
2233
2247
2234 # Merge global, repo and incoming settings.
2248 # Merge global, repo and incoming settings.
2235 new_settings = settings_model.get_global_settings()
2249 new_settings = settings_model.get_global_settings()
2236 new_settings.update(settings_model.get_repo_settings())
2250 new_settings.update(settings_model.get_repo_settings())
2237 new_settings.update(settings)
2251 new_settings.update(settings)
2238
2252
2239 # Update the settings.
2253 # Update the settings.
2240 inherit_global_settings = new_settings.get(
2254 inherit_global_settings = new_settings.get(
2241 'inherit_global_settings', False)
2255 'inherit_global_settings', False)
2242 settings_model.create_or_update_repo_settings(
2256 settings_model.create_or_update_repo_settings(
2243 new_settings, inherit_global_settings=inherit_global_settings)
2257 new_settings, inherit_global_settings=inherit_global_settings)
2244 Session().commit()
2258 Session().commit()
2245 except Exception:
2259 except Exception:
2246 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2260 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2247 log.exception(msg)
2261 log.exception(msg)
2248 raise JSONRPCError(msg)
2262 raise JSONRPCError(msg)
2249
2263
2250 # Indicate success.
2264 # Indicate success.
2251 return True
2265 return True
2252
2266
2253
2267
2254 @jsonrpc_method()
2268 @jsonrpc_method()
2255 def maintenance(request, apiuser, repoid):
2269 def maintenance(request, apiuser, repoid):
2256 """
2270 """
2257 Triggers a maintenance on the given repository.
2271 Triggers a maintenance on the given repository.
2258
2272
2259 This command can only be run using an |authtoken| with admin
2273 This command can only be run using an |authtoken| with admin
2260 rights to the specified repository. For more information,
2274 rights to the specified repository. For more information,
2261 see :ref:`config-token-ref`.
2275 see :ref:`config-token-ref`.
2262
2276
2263 This command takes the following options:
2277 This command takes the following options:
2264
2278
2265 :param apiuser: This is filled automatically from the |authtoken|.
2279 :param apiuser: This is filled automatically from the |authtoken|.
2266 :type apiuser: AuthUser
2280 :type apiuser: AuthUser
2267 :param repoid: The repository name or repository ID.
2281 :param repoid: The repository name or repository ID.
2268 :type repoid: str or int
2282 :type repoid: str or int
2269
2283
2270 Example output:
2284 Example output:
2271
2285
2272 .. code-block:: bash
2286 .. code-block:: bash
2273
2287
2274 id : <id_given_in_input>
2288 id : <id_given_in_input>
2275 result : {
2289 result : {
2276 "msg": "executed maintenance command",
2290 "msg": "executed maintenance command",
2277 "executed_actions": [
2291 "executed_actions": [
2278 <action_message>, <action_message2>...
2292 <action_message>, <action_message2>...
2279 ],
2293 ],
2280 "repository": "<repository name>"
2294 "repository": "<repository name>"
2281 }
2295 }
2282 error : null
2296 error : null
2283
2297
2284 Example error output:
2298 Example error output:
2285
2299
2286 .. code-block:: bash
2300 .. code-block:: bash
2287
2301
2288 id : <id_given_in_input>
2302 id : <id_given_in_input>
2289 result : null
2303 result : null
2290 error : {
2304 error : {
2291 "Unable to execute maintenance on `<reponame>`"
2305 "Unable to execute maintenance on `<reponame>`"
2292 }
2306 }
2293
2307
2294 """
2308 """
2295
2309
2296 repo = get_repo_or_error(repoid)
2310 repo = get_repo_or_error(repoid)
2297 if not has_superadmin_permission(apiuser):
2311 if not has_superadmin_permission(apiuser):
2298 _perms = ('repository.admin',)
2312 _perms = ('repository.admin',)
2299 validate_repo_permissions(apiuser, repoid, repo, _perms)
2313 validate_repo_permissions(apiuser, repoid, repo, _perms)
2300
2314
2301 try:
2315 try:
2302 maintenance = repo_maintenance.RepoMaintenance()
2316 maintenance = repo_maintenance.RepoMaintenance()
2303 executed_actions = maintenance.execute(repo)
2317 executed_actions = maintenance.execute(repo)
2304
2318
2305 return {
2319 return {
2306 'msg': 'executed maintenance command',
2320 'msg': 'executed maintenance command',
2307 'executed_actions': executed_actions,
2321 'executed_actions': executed_actions,
2308 'repository': repo.repo_name
2322 'repository': repo.repo_name
2309 }
2323 }
2310 except Exception:
2324 except Exception:
2311 log.exception("Exception occurred while trying to run maintenance")
2325 log.exception("Exception occurred while trying to run maintenance")
2312 raise JSONRPCError(
2326 raise JSONRPCError(
2313 'Unable to execute maintenance on `%s`' % repo.repo_name)
2327 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,189 +1,186 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 logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode.lib.celerylib.utils import get_task_id
32 from rhodecode.lib.celerylib.utils import get_task_id
33
33
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, CSRFRequired, NotAnonymous,
36 LoginRequired, CSRFRequired, NotAnonymous,
37 HasPermissionAny, HasRepoGroupPermissionAny)
37 HasPermissionAny, HasRepoGroupPermissionAny)
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib.utils import repo_name_slug
40 from rhodecode.lib.utils2 import safe_int, safe_unicode
40 from rhodecode.lib.utils2 import safe_int, safe_unicode
41 from rhodecode.model.forms import RepoForm
41 from rhodecode.model.forms import RepoForm
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
43 from rhodecode.model.scm import RepoList, RepoGroupList, ScmModel
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.db import Repository, RepoGroup
45 from rhodecode.model.db import Repository, RepoGroup
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class AdminReposView(BaseAppView, DataGridAppView):
50 class AdminReposView(BaseAppView, DataGridAppView):
51
51
52 def load_default_context(self):
52 def load_default_context(self):
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54
54
55 return c
55 return c
56
56
57 def _load_form_data(self, c):
57 def _load_form_data(self, c):
58 acl_groups = RepoGroupList(RepoGroup.query().all(),
58 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 perm_set=['group.write', 'group.admin'])
59 perm_set=['group.write', 'group.admin'])
60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
61 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
62 c.landing_revs_choices, c.landing_revs = \
63 ScmModel().get_repo_landing_revs(self.request.translate)
64 c.personal_repo_group = self._rhodecode_user.personal_repo_group
62 c.personal_repo_group = self._rhodecode_user.personal_repo_group
65
63
66 @LoginRequired()
64 @LoginRequired()
67 @NotAnonymous()
65 @NotAnonymous()
68 # perms check inside
66 # perms check inside
69 @view_config(
67 @view_config(
70 route_name='repos', request_method='GET',
68 route_name='repos', request_method='GET',
71 renderer='rhodecode:templates/admin/repos/repos.mako')
69 renderer='rhodecode:templates/admin/repos/repos.mako')
72 def repository_list(self):
70 def repository_list(self):
73 c = self.load_default_context()
71 c = self.load_default_context()
74
72
75 repo_list = Repository.get_all_repos()
73 repo_list = Repository.get_all_repos()
76 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
74 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
77 repos_data = RepoModel().get_repos_as_dict(
75 repos_data = RepoModel().get_repos_as_dict(
78 repo_list=c.repo_list, admin=True, super_user_actions=True)
76 repo_list=c.repo_list, admin=True, super_user_actions=True)
79 # json used to render the grid
77 # json used to render the grid
80 c.data = json.dumps(repos_data)
78 c.data = json.dumps(repos_data)
81
79
82 return self._get_template_context(c)
80 return self._get_template_context(c)
83
81
84 @LoginRequired()
82 @LoginRequired()
85 @NotAnonymous()
83 @NotAnonymous()
86 # perms check inside
84 # perms check inside
87 @view_config(
85 @view_config(
88 route_name='repo_new', request_method='GET',
86 route_name='repo_new', request_method='GET',
89 renderer='rhodecode:templates/admin/repos/repo_add.mako')
87 renderer='rhodecode:templates/admin/repos/repo_add.mako')
90 def repository_new(self):
88 def repository_new(self):
91 c = self.load_default_context()
89 c = self.load_default_context()
92
90
93 new_repo = self.request.GET.get('repo', '')
91 new_repo = self.request.GET.get('repo', '')
94 parent_group = safe_int(self.request.GET.get('parent_group'))
92 parent_group = safe_int(self.request.GET.get('parent_group'))
95 _gr = RepoGroup.get(parent_group)
93 _gr = RepoGroup.get(parent_group)
96
94
97 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
95 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
98 # you're not super admin nor have global create permissions,
96 # you're not super admin nor have global create permissions,
99 # but maybe you have at least write permission to a parent group ?
97 # but maybe you have at least write permission to a parent group ?
100
98
101 gr_name = _gr.group_name if _gr else None
99 gr_name = _gr.group_name if _gr else None
102 # create repositories with write permission on group is set to true
100 # create repositories with write permission on group is set to true
103 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
101 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
104 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
102 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
105 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
103 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
106 if not (group_admin or (group_write and create_on_write)):
104 if not (group_admin or (group_write and create_on_write)):
107 raise HTTPForbidden()
105 raise HTTPForbidden()
108
106
109 self._load_form_data(c)
107 self._load_form_data(c)
110 c.new_repo = repo_name_slug(new_repo)
108 c.new_repo = repo_name_slug(new_repo)
111
109
112 # apply the defaults from defaults page
110 # apply the defaults from defaults page
113 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
111 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
114 # set checkbox to autochecked
112 # set checkbox to autochecked
115 defaults['repo_copy_permissions'] = True
113 defaults['repo_copy_permissions'] = True
116
114
117 parent_group_choice = '-1'
115 parent_group_choice = '-1'
118 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
116 if not self._rhodecode_user.is_admin and self._rhodecode_user.personal_repo_group:
119 parent_group_choice = self._rhodecode_user.personal_repo_group
117 parent_group_choice = self._rhodecode_user.personal_repo_group
120
118
121 if parent_group and _gr:
119 if parent_group and _gr:
122 if parent_group in [x[0] for x in c.repo_groups]:
120 if parent_group in [x[0] for x in c.repo_groups]:
123 parent_group_choice = safe_unicode(parent_group)
121 parent_group_choice = safe_unicode(parent_group)
124
122
125 defaults.update({'repo_group': parent_group_choice})
123 defaults.update({'repo_group': parent_group_choice})
126
124
127 data = render('rhodecode:templates/admin/repos/repo_add.mako',
125 data = render('rhodecode:templates/admin/repos/repo_add.mako',
128 self._get_template_context(c), self.request)
126 self._get_template_context(c), self.request)
129 html = formencode.htmlfill.render(
127 html = formencode.htmlfill.render(
130 data,
128 data,
131 defaults=defaults,
129 defaults=defaults,
132 encoding="UTF-8",
130 encoding="UTF-8",
133 force_defaults=False
131 force_defaults=False
134 )
132 )
135 return Response(html)
133 return Response(html)
136
134
137 @LoginRequired()
135 @LoginRequired()
138 @NotAnonymous()
136 @NotAnonymous()
139 @CSRFRequired()
137 @CSRFRequired()
140 # perms check inside
138 # perms check inside
141 @view_config(
139 @view_config(
142 route_name='repo_create', request_method='POST',
140 route_name='repo_create', request_method='POST',
143 renderer='rhodecode:templates/admin/repos/repos.mako')
141 renderer='rhodecode:templates/admin/repos/repos.mako')
144 def repository_create(self):
142 def repository_create(self):
145 c = self.load_default_context()
143 c = self.load_default_context()
146
144
147 form_result = {}
145 form_result = {}
148 self._load_form_data(c)
146 self._load_form_data(c)
149
147
150 try:
148 try:
151 # CanWriteToGroup validators checks permissions of this POST
149 # CanWriteToGroup validators checks permissions of this POST
152 form = RepoForm(
150 form = RepoForm(
153 self.request.translate, repo_groups=c.repo_groups_choices,
151 self.request.translate, repo_groups=c.repo_groups_choices)()
154 landing_revs=c.landing_revs_choices)()
155 form_result = form.to_python(dict(self.request.POST))
152 form_result = form.to_python(dict(self.request.POST))
156 copy_permissions = form_result.get('repo_copy_permissions')
153 copy_permissions = form_result.get('repo_copy_permissions')
157 # create is done sometimes async on celery, db transaction
154 # create is done sometimes async on celery, db transaction
158 # management is handled there.
155 # management is handled there.
159 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
156 task = RepoModel().create(form_result, self._rhodecode_user.user_id)
160 task_id = get_task_id(task)
157 task_id = get_task_id(task)
161 except formencode.Invalid as errors:
158 except formencode.Invalid as errors:
162 data = render('rhodecode:templates/admin/repos/repo_add.mako',
159 data = render('rhodecode:templates/admin/repos/repo_add.mako',
163 self._get_template_context(c), self.request)
160 self._get_template_context(c), self.request)
164 html = formencode.htmlfill.render(
161 html = formencode.htmlfill.render(
165 data,
162 data,
166 defaults=errors.value,
163 defaults=errors.value,
167 errors=errors.error_dict or {},
164 errors=errors.error_dict or {},
168 prefix_error=False,
165 prefix_error=False,
169 encoding="UTF-8",
166 encoding="UTF-8",
170 force_defaults=False
167 force_defaults=False
171 )
168 )
172 return Response(html)
169 return Response(html)
173
170
174 except Exception as e:
171 except Exception as e:
175 msg = self._log_creation_exception(e, form_result.get('repo_name'))
172 msg = self._log_creation_exception(e, form_result.get('repo_name'))
176 h.flash(msg, category='error')
173 h.flash(msg, category='error')
177 raise HTTPFound(h.route_path('home'))
174 raise HTTPFound(h.route_path('home'))
178
175
179 repo_name = form_result.get('repo_name_full')
176 repo_name = form_result.get('repo_name_full')
180
177
181 affected_user_ids = [self._rhodecode_user.user_id]
178 affected_user_ids = [self._rhodecode_user.user_id]
182 if copy_permissions:
179 if copy_permissions:
183 # permission flush is done in repo creating
180 # permission flush is done in repo creating
184 pass
181 pass
185 events.trigger(events.UserPermissionsChange(affected_user_ids))
182 events.trigger(events.UserPermissionsChange(affected_user_ids))
186
183
187 raise HTTPFound(
184 raise HTTPFound(
188 h.route_path('repo_creating', repo_name=repo_name,
185 h.route_path('repo_creating', repo_name=repo_name,
189 _query=dict(task_id=task_id)))
186 _query=dict(task_id=task_id)))
@@ -1,266 +1,264 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 datetime
22 import datetime
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
35 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
35 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.celerylib.utils import get_task_id
37 from rhodecode.lib.celerylib.utils import get_task_id
38 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
38 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.forms import RepoForkForm
40 from rhodecode.model.forms import RepoForkForm
41 from rhodecode.model.scm import ScmModel, RepoGroupList
41 from rhodecode.model.scm import ScmModel, RepoGroupList
42 from rhodecode.lib.utils2 import safe_int, safe_unicode
42 from rhodecode.lib.utils2 import safe_int, safe_unicode
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class RepoForksView(RepoAppView, DataGridAppView):
47 class RepoForksView(RepoAppView, DataGridAppView):
48
48
49 def load_default_context(self):
49 def load_default_context(self):
50 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c = self._get_local_tmpl_context(include_app_defaults=True)
51 c.rhodecode_repo = self.rhodecode_vcs_repo
51 c.rhodecode_repo = self.rhodecode_vcs_repo
52
52
53 acl_groups = RepoGroupList(
53 acl_groups = RepoGroupList(
54 RepoGroup.query().all(),
54 RepoGroup.query().all(),
55 perm_set=['group.write', 'group.admin'])
55 perm_set=['group.write', 'group.admin'])
56 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
56 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
57 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
57 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
58 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
58
59 self.request.translate)
60 c.landing_revs_choices = choices
61 c.personal_repo_group = c.rhodecode_user.personal_repo_group
59 c.personal_repo_group = c.rhodecode_user.personal_repo_group
62
60
63 return c
61 return c
64
62
65 @LoginRequired()
63 @LoginRequired()
66 @HasRepoPermissionAnyDecorator(
64 @HasRepoPermissionAnyDecorator(
67 'repository.read', 'repository.write', 'repository.admin')
65 'repository.read', 'repository.write', 'repository.admin')
68 @view_config(
66 @view_config(
69 route_name='repo_forks_show_all', request_method='GET',
67 route_name='repo_forks_show_all', request_method='GET',
70 renderer='rhodecode:templates/forks/forks.mako')
68 renderer='rhodecode:templates/forks/forks.mako')
71 def repo_forks_show_all(self):
69 def repo_forks_show_all(self):
72 c = self.load_default_context()
70 c = self.load_default_context()
73 return self._get_template_context(c)
71 return self._get_template_context(c)
74
72
75 @LoginRequired()
73 @LoginRequired()
76 @HasRepoPermissionAnyDecorator(
74 @HasRepoPermissionAnyDecorator(
77 'repository.read', 'repository.write', 'repository.admin')
75 'repository.read', 'repository.write', 'repository.admin')
78 @view_config(
76 @view_config(
79 route_name='repo_forks_data', request_method='GET',
77 route_name='repo_forks_data', request_method='GET',
80 renderer='json_ext', xhr=True)
78 renderer='json_ext', xhr=True)
81 def repo_forks_data(self):
79 def repo_forks_data(self):
82 _ = self.request.translate
80 _ = self.request.translate
83 self.load_default_context()
81 self.load_default_context()
84 column_map = {
82 column_map = {
85 'fork_name': 'repo_name',
83 'fork_name': 'repo_name',
86 'fork_date': 'created_on',
84 'fork_date': 'created_on',
87 'last_activity': 'updated_on'
85 'last_activity': 'updated_on'
88 }
86 }
89 draw, start, limit = self._extract_chunk(self.request)
87 draw, start, limit = self._extract_chunk(self.request)
90 search_q, order_by, order_dir = self._extract_ordering(
88 search_q, order_by, order_dir = self._extract_ordering(
91 self.request, column_map=column_map)
89 self.request, column_map=column_map)
92
90
93 acl_check = HasRepoPermissionAny(
91 acl_check = HasRepoPermissionAny(
94 'repository.read', 'repository.write', 'repository.admin')
92 'repository.read', 'repository.write', 'repository.admin')
95 repo_id = self.db_repo.repo_id
93 repo_id = self.db_repo.repo_id
96 allowed_ids = [-1]
94 allowed_ids = [-1]
97 for f in Repository.query().filter(Repository.fork_id == repo_id):
95 for f in Repository.query().filter(Repository.fork_id == repo_id):
98 if acl_check(f.repo_name, 'get forks check'):
96 if acl_check(f.repo_name, 'get forks check'):
99 allowed_ids.append(f.repo_id)
97 allowed_ids.append(f.repo_id)
100
98
101 forks_data_total_count = Repository.query()\
99 forks_data_total_count = Repository.query()\
102 .filter(Repository.fork_id == repo_id)\
100 .filter(Repository.fork_id == repo_id)\
103 .filter(Repository.repo_id.in_(allowed_ids))\
101 .filter(Repository.repo_id.in_(allowed_ids))\
104 .count()
102 .count()
105
103
106 # json generate
104 # json generate
107 base_q = Repository.query()\
105 base_q = Repository.query()\
108 .filter(Repository.fork_id == repo_id)\
106 .filter(Repository.fork_id == repo_id)\
109 .filter(Repository.repo_id.in_(allowed_ids))\
107 .filter(Repository.repo_id.in_(allowed_ids))\
110
108
111 if search_q:
109 if search_q:
112 like_expression = u'%{}%'.format(safe_unicode(search_q))
110 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 base_q = base_q.filter(or_(
111 base_q = base_q.filter(or_(
114 Repository.repo_name.ilike(like_expression),
112 Repository.repo_name.ilike(like_expression),
115 Repository.description.ilike(like_expression),
113 Repository.description.ilike(like_expression),
116 ))
114 ))
117
115
118 forks_data_total_filtered_count = base_q.count()
116 forks_data_total_filtered_count = base_q.count()
119
117
120 sort_col = getattr(Repository, order_by, None)
118 sort_col = getattr(Repository, order_by, None)
121 if sort_col:
119 if sort_col:
122 if order_dir == 'asc':
120 if order_dir == 'asc':
123 # handle null values properly to order by NULL last
121 # handle null values properly to order by NULL last
124 if order_by in ['last_activity']:
122 if order_by in ['last_activity']:
125 sort_col = coalesce(sort_col, datetime.date.max)
123 sort_col = coalesce(sort_col, datetime.date.max)
126 sort_col = sort_col.asc()
124 sort_col = sort_col.asc()
127 else:
125 else:
128 # handle null values properly to order by NULL last
126 # handle null values properly to order by NULL last
129 if order_by in ['last_activity']:
127 if order_by in ['last_activity']:
130 sort_col = coalesce(sort_col, datetime.date.min)
128 sort_col = coalesce(sort_col, datetime.date.min)
131 sort_col = sort_col.desc()
129 sort_col = sort_col.desc()
132
130
133 base_q = base_q.order_by(sort_col)
131 base_q = base_q.order_by(sort_col)
134 base_q = base_q.offset(start).limit(limit)
132 base_q = base_q.offset(start).limit(limit)
135
133
136 fork_list = base_q.all()
134 fork_list = base_q.all()
137
135
138 def fork_actions(fork):
136 def fork_actions(fork):
139 url_link = h.route_path(
137 url_link = h.route_path(
140 'repo_compare',
138 'repo_compare',
141 repo_name=fork.repo_name,
139 repo_name=fork.repo_name,
142 source_ref_type=self.db_repo.landing_rev[0],
140 source_ref_type=self.db_repo.landing_rev[0],
143 source_ref=self.db_repo.landing_rev[1],
141 source_ref=self.db_repo.landing_rev[1],
144 target_ref_type=self.db_repo.landing_rev[0],
142 target_ref_type=self.db_repo.landing_rev[0],
145 target_ref=self.db_repo.landing_rev[1],
143 target_ref=self.db_repo.landing_rev[1],
146 _query=dict(merge=1, target_repo=f.repo_name))
144 _query=dict(merge=1, target_repo=f.repo_name))
147 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
145 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
148
146
149 def fork_name(fork):
147 def fork_name(fork):
150 return h.link_to(fork.repo_name,
148 return h.link_to(fork.repo_name,
151 h.route_path('repo_summary', repo_name=fork.repo_name))
149 h.route_path('repo_summary', repo_name=fork.repo_name))
152
150
153 forks_data = []
151 forks_data = []
154 for fork in fork_list:
152 for fork in fork_list:
155 forks_data.append({
153 forks_data.append({
156 "username": h.gravatar_with_user(self.request, fork.user.username),
154 "username": h.gravatar_with_user(self.request, fork.user.username),
157 "fork_name": fork_name(fork),
155 "fork_name": fork_name(fork),
158 "description": fork.description_safe,
156 "description": fork.description_safe,
159 "fork_date": h.age_component(fork.created_on, time_is_local=True),
157 "fork_date": h.age_component(fork.created_on, time_is_local=True),
160 "last_activity": h.format_date(fork.updated_on),
158 "last_activity": h.format_date(fork.updated_on),
161 "action": fork_actions(fork),
159 "action": fork_actions(fork),
162 })
160 })
163
161
164 data = ({
162 data = ({
165 'draw': draw,
163 'draw': draw,
166 'data': forks_data,
164 'data': forks_data,
167 'recordsTotal': forks_data_total_count,
165 'recordsTotal': forks_data_total_count,
168 'recordsFiltered': forks_data_total_filtered_count,
166 'recordsFiltered': forks_data_total_filtered_count,
169 })
167 })
170
168
171 return data
169 return data
172
170
173 @LoginRequired()
171 @LoginRequired()
174 @NotAnonymous()
172 @NotAnonymous()
175 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
173 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
176 @HasRepoPermissionAnyDecorator(
174 @HasRepoPermissionAnyDecorator(
177 'repository.read', 'repository.write', 'repository.admin')
175 'repository.read', 'repository.write', 'repository.admin')
178 @view_config(
176 @view_config(
179 route_name='repo_fork_new', request_method='GET',
177 route_name='repo_fork_new', request_method='GET',
180 renderer='rhodecode:templates/forks/forks.mako')
178 renderer='rhodecode:templates/forks/forks.mako')
181 def repo_fork_new(self):
179 def repo_fork_new(self):
182 c = self.load_default_context()
180 c = self.load_default_context()
183
181
184 defaults = RepoModel()._get_defaults(self.db_repo_name)
182 defaults = RepoModel()._get_defaults(self.db_repo_name)
185 # alter the description to indicate a fork
183 # alter the description to indicate a fork
186 defaults['description'] = (
184 defaults['description'] = (
187 'fork of repository: %s \n%s' % (
185 'fork of repository: %s \n%s' % (
188 defaults['repo_name'], defaults['description']))
186 defaults['repo_name'], defaults['description']))
189 # add suffix to fork
187 # add suffix to fork
190 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
188 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
191
189
192 data = render('rhodecode:templates/forks/fork.mako',
190 data = render('rhodecode:templates/forks/fork.mako',
193 self._get_template_context(c), self.request)
191 self._get_template_context(c), self.request)
194 html = formencode.htmlfill.render(
192 html = formencode.htmlfill.render(
195 data,
193 data,
196 defaults=defaults,
194 defaults=defaults,
197 encoding="UTF-8",
195 encoding="UTF-8",
198 force_defaults=False
196 force_defaults=False
199 )
197 )
200 return Response(html)
198 return Response(html)
201
199
202 @LoginRequired()
200 @LoginRequired()
203 @NotAnonymous()
201 @NotAnonymous()
204 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
202 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
205 @HasRepoPermissionAnyDecorator(
203 @HasRepoPermissionAnyDecorator(
206 'repository.read', 'repository.write', 'repository.admin')
204 'repository.read', 'repository.write', 'repository.admin')
207 @CSRFRequired()
205 @CSRFRequired()
208 @view_config(
206 @view_config(
209 route_name='repo_fork_create', request_method='POST',
207 route_name='repo_fork_create', request_method='POST',
210 renderer='rhodecode:templates/forks/fork.mako')
208 renderer='rhodecode:templates/forks/fork.mako')
211 def repo_fork_create(self):
209 def repo_fork_create(self):
212 _ = self.request.translate
210 _ = self.request.translate
213 c = self.load_default_context()
211 c = self.load_default_context()
214
212
215 _form = RepoForkForm(self.request.translate, old_data={'repo_type': self.db_repo.repo_type},
213 _form = RepoForkForm(self.request.translate,
216 repo_groups=c.repo_groups_choices,
214 old_data={'repo_type': self.db_repo.repo_type},
217 landing_revs=c.landing_revs_choices)()
215 repo_groups=c.repo_groups_choices)()
218 post_data = dict(self.request.POST)
216 post_data = dict(self.request.POST)
219
217
220 # forbid injecting other repo by forging a request
218 # forbid injecting other repo by forging a request
221 post_data['fork_parent_id'] = self.db_repo.repo_id
219 post_data['fork_parent_id'] = self.db_repo.repo_id
222
220
223 form_result = {}
221 form_result = {}
224 task_id = None
222 task_id = None
225 try:
223 try:
226 form_result = _form.to_python(post_data)
224 form_result = _form.to_python(post_data)
227 copy_permissions = form_result.get('copy_permissions')
225 copy_permissions = form_result.get('copy_permissions')
228 # create fork is done sometimes async on celery, db transaction
226 # create fork is done sometimes async on celery, db transaction
229 # management is handled there.
227 # management is handled there.
230 task = RepoModel().create_fork(
228 task = RepoModel().create_fork(
231 form_result, c.rhodecode_user.user_id)
229 form_result, c.rhodecode_user.user_id)
232
230
233 task_id = get_task_id(task)
231 task_id = get_task_id(task)
234 except formencode.Invalid as errors:
232 except formencode.Invalid as errors:
235 c.rhodecode_db_repo = self.db_repo
233 c.rhodecode_db_repo = self.db_repo
236
234
237 data = render('rhodecode:templates/forks/fork.mako',
235 data = render('rhodecode:templates/forks/fork.mako',
238 self._get_template_context(c), self.request)
236 self._get_template_context(c), self.request)
239 html = formencode.htmlfill.render(
237 html = formencode.htmlfill.render(
240 data,
238 data,
241 defaults=errors.value,
239 defaults=errors.value,
242 errors=errors.error_dict or {},
240 errors=errors.error_dict or {},
243 prefix_error=False,
241 prefix_error=False,
244 encoding="UTF-8",
242 encoding="UTF-8",
245 force_defaults=False
243 force_defaults=False
246 )
244 )
247 return Response(html)
245 return Response(html)
248 except Exception:
246 except Exception:
249 log.exception(
247 log.exception(
250 u'Exception while trying to fork the repository %s', self.db_repo_name)
248 u'Exception while trying to fork the repository %s', self.db_repo_name)
251 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
249 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
252 h.flash(msg, category='error')
250 h.flash(msg, category='error')
253 raise HTTPFound(h.route_path('home'))
251 raise HTTPFound(h.route_path('home'))
254
252
255 repo_name = form_result.get('repo_name_full', self.db_repo_name)
253 repo_name = form_result.get('repo_name_full', self.db_repo_name)
256
254
257 affected_user_ids = [self._rhodecode_user.user_id]
255 affected_user_ids = [self._rhodecode_user.user_id]
258 if copy_permissions:
256 if copy_permissions:
259 # permission flush is done in repo creating
257 # permission flush is done in repo creating
260 pass
258 pass
261
259
262 events.trigger(events.UserPermissionsChange(affected_user_ids))
260 events.trigger(events.UserPermissionsChange(affected_user_ids))
263
261
264 raise HTTPFound(
262 raise HTTPFound(
265 h.route_path('repo_creating', repo_name=repo_name,
263 h.route_path('repo_creating', repo_name=repo_name,
266 _query=dict(task_id=task_id)))
264 _query=dict(task_id=task_id)))
@@ -1,396 +1,376 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 string
22 import string
23 import time
24
23 import rhodecode
25 import rhodecode
24
26
25 from pyramid.view import view_config
27 from pyramid.view import view_config
26
28
27 from rhodecode.lib.view_utils import get_format_ref_id
29 from rhodecode.lib.view_utils import get_format_ref_id
28 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
29 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
30 from rhodecode.lib import helpers as h, rc_cache
32 from rhodecode.lib import helpers as h, rc_cache
31 from rhodecode.lib.utils2 import safe_str, safe_int
33 from rhodecode.lib.utils2 import safe_str, safe_int
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
35 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
34 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.exceptions import (
38 from rhodecode.lib.vcs.exceptions import (
37 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
39 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 from rhodecode.model.db import Statistics, CacheKey, User
40 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
40 from rhodecode.model.repo import ReadmeFinder
42 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.scm import ScmModel
43 from rhodecode.model.scm import ScmModel
42
44
43 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
44
46
45
47
46 class RepoSummaryView(RepoAppView):
48 class RepoSummaryView(RepoAppView):
47
49
48 def load_default_context(self):
50 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
51 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c.rhodecode_repo = None
52 c.rhodecode_repo = None
51 if not c.repository_requirements_missing:
53 if not c.repository_requirements_missing:
52 c.rhodecode_repo = self.rhodecode_vcs_repo
54 c.rhodecode_repo = self.rhodecode_vcs_repo
53 return c
55 return c
54
56
55 def _get_readme_data(self, db_repo, renderer_type):
57 def _get_readme_data(self, db_repo, renderer_type):
56
57 log.debug('Looking for README file')
58 log.debug('Looking for README file')
59 landing_commit = db_repo.get_landing_commit()
60 if isinstance(landing_commit, EmptyCommit):
61 return None, None
58
62
59 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
63 cache_namespace_uid = 'cache_repo_instance.{}_{}'.format(
60 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
64 db_repo.repo_id, CacheKey.CACHE_TYPE_README)
61 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
62 repo_id=self.db_repo.repo_id)
63 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
65 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
66 start = time.time()
64
67
65 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
68 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
66 def generate_repo_readme(repo_id, _repo_name, _renderer_type):
69 def generate_repo_readme(repo_id, commit_id, _repo_name, _renderer_type):
67 readme_data = None
70 readme_data = None
68 readme_node = None
69 readme_filename = None
71 readme_filename = None
70 commit = self._get_landing_commit_or_none(db_repo)
72
71 if commit:
73 commit = db_repo.get_commit(commit_id)
72 log.debug("Searching for a README file.")
74 log.debug("Searching for a README file at commit %s.", commit_id)
73 readme_node = ReadmeFinder(_renderer_type).search(commit)
75 readme_node = ReadmeFinder(_renderer_type).search(commit)
76
74 if readme_node:
77 if readme_node:
75 log.debug('Found README node: %s', readme_node)
78 log.debug('Found README node: %s', readme_node)
76 relative_urls = {
79 relative_urls = {
77 'raw': h.route_path(
80 'raw': h.route_path(
78 'repo_file_raw', repo_name=_repo_name,
81 'repo_file_raw', repo_name=_repo_name,
79 commit_id=commit.raw_id, f_path=readme_node.path),
82 commit_id=commit.raw_id, f_path=readme_node.path),
80 'standard': h.route_path(
83 'standard': h.route_path(
81 'repo_files', repo_name=_repo_name,
84 'repo_files', repo_name=_repo_name,
82 commit_id=commit.raw_id, f_path=readme_node.path),
85 commit_id=commit.raw_id, f_path=readme_node.path),
83 }
86 }
84 readme_data = self._render_readme_or_none(
87 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
85 commit, readme_node, relative_urls)
86 readme_filename = readme_node.unicode_path
88 readme_filename = readme_node.unicode_path
87
89
88 return readme_data, readme_filename
90 return readme_data, readme_filename
89
91
90 inv_context_manager = rc_cache.InvalidationContext(
92 readme_data, readme_filename = generate_repo_readme(
91 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
93 db_repo.repo_id, landing_commit.raw_id, db_repo.repo_name, renderer_type,)
92 with inv_context_manager as invalidation_context:
94 compute_time = time.time() - start
93 args = (db_repo.repo_id, db_repo.repo_name, renderer_type,)
95 log.debug('Repo readme generated and computed in %.4fs', compute_time)
94 # re-compute and store cache if we get invalidate signal
96 return readme_data, readme_filename
95 if invalidation_context.should_invalidate():
96 instance = generate_repo_readme.refresh(*args)
97 else:
98 instance = generate_repo_readme(*args)
99
100 log.debug(
101 'Repo readme generated and computed in %.4fs',
102 inv_context_manager.compute_time)
103 return instance
104
105 def _get_landing_commit_or_none(self, db_repo):
106 log.debug("Getting the landing commit.")
107 try:
108 commit = db_repo.get_landing_commit()
109 if not isinstance(commit, EmptyCommit):
110 return commit
111 else:
112 log.debug("Repository is empty, no README to render.")
113 except CommitError:
114 log.exception(
115 "Problem getting commit when trying to render the README.")
116
97
117 def _render_readme_or_none(self, commit, readme_node, relative_urls):
98 def _render_readme_or_none(self, commit, readme_node, relative_urls):
118 log.debug(
99 log.debug('Found README file `%s` rendering...', readme_node.path)
119 'Found README file `%s` rendering...', readme_node.path)
120 renderer = MarkupRenderer()
100 renderer = MarkupRenderer()
121 try:
101 try:
122 html_source = renderer.render(
102 html_source = renderer.render(
123 readme_node.content, filename=readme_node.path)
103 readme_node.content, filename=readme_node.path)
124 if relative_urls:
104 if relative_urls:
125 return relative_links(html_source, relative_urls)
105 return relative_links(html_source, relative_urls)
126 return html_source
106 return html_source
127 except Exception:
107 except Exception:
128 log.exception(
108 log.exception(
129 "Exception while trying to render the README")
109 "Exception while trying to render the README")
130
110
131 def _load_commits_context(self, c):
111 def _load_commits_context(self, c):
132 p = safe_int(self.request.GET.get('page'), 1)
112 p = safe_int(self.request.GET.get('page'), 1)
133 size = safe_int(self.request.GET.get('size'), 10)
113 size = safe_int(self.request.GET.get('size'), 10)
134
114
135 def url_generator(**kw):
115 def url_generator(**kw):
136 query_params = {
116 query_params = {
137 'size': size
117 'size': size
138 }
118 }
139 query_params.update(kw)
119 query_params.update(kw)
140 return h.route_path(
120 return h.route_path(
141 'repo_summary_commits',
121 'repo_summary_commits',
142 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
122 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
143
123
144 pre_load = ['author', 'branch', 'date', 'message']
124 pre_load = ['author', 'branch', 'date', 'message']
145 try:
125 try:
146 collection = self.rhodecode_vcs_repo.get_commits(
126 collection = self.rhodecode_vcs_repo.get_commits(
147 pre_load=pre_load, translate_tags=False)
127 pre_load=pre_load, translate_tags=False)
148 except EmptyRepositoryError:
128 except EmptyRepositoryError:
149 collection = self.rhodecode_vcs_repo
129 collection = self.rhodecode_vcs_repo
150
130
151 c.repo_commits = h.RepoPage(
131 c.repo_commits = h.RepoPage(
152 collection, page=p, items_per_page=size, url=url_generator)
132 collection, page=p, items_per_page=size, url=url_generator)
153 page_ids = [x.raw_id for x in c.repo_commits]
133 page_ids = [x.raw_id for x in c.repo_commits]
154 c.comments = self.db_repo.get_comments(page_ids)
134 c.comments = self.db_repo.get_comments(page_ids)
155 c.statuses = self.db_repo.statuses(page_ids)
135 c.statuses = self.db_repo.statuses(page_ids)
156
136
157 def _prepare_and_set_clone_url(self, c):
137 def _prepare_and_set_clone_url(self, c):
158 username = ''
138 username = ''
159 if self._rhodecode_user.username != User.DEFAULT_USER:
139 if self._rhodecode_user.username != User.DEFAULT_USER:
160 username = safe_str(self._rhodecode_user.username)
140 username = safe_str(self._rhodecode_user.username)
161
141
162 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
142 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
163 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
143 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
164
144
165 if '{repo}' in _def_clone_uri:
145 if '{repo}' in _def_clone_uri:
166 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
146 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
167 elif '{repoid}' in _def_clone_uri:
147 elif '{repoid}' in _def_clone_uri:
168 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
148 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
169
149
170 c.clone_repo_url = self.db_repo.clone_url(
150 c.clone_repo_url = self.db_repo.clone_url(
171 user=username, uri_tmpl=_def_clone_uri)
151 user=username, uri_tmpl=_def_clone_uri)
172 c.clone_repo_url_id = self.db_repo.clone_url(
152 c.clone_repo_url_id = self.db_repo.clone_url(
173 user=username, uri_tmpl=_def_clone_uri_id)
153 user=username, uri_tmpl=_def_clone_uri_id)
174 c.clone_repo_url_ssh = self.db_repo.clone_url(
154 c.clone_repo_url_ssh = self.db_repo.clone_url(
175 uri_tmpl=_def_clone_uri_ssh, ssh=True)
155 uri_tmpl=_def_clone_uri_ssh, ssh=True)
176
156
177 @LoginRequired()
157 @LoginRequired()
178 @HasRepoPermissionAnyDecorator(
158 @HasRepoPermissionAnyDecorator(
179 'repository.read', 'repository.write', 'repository.admin')
159 'repository.read', 'repository.write', 'repository.admin')
180 @view_config(
160 @view_config(
181 route_name='repo_summary_commits', request_method='GET',
161 route_name='repo_summary_commits', request_method='GET',
182 renderer='rhodecode:templates/summary/summary_commits.mako')
162 renderer='rhodecode:templates/summary/summary_commits.mako')
183 def summary_commits(self):
163 def summary_commits(self):
184 c = self.load_default_context()
164 c = self.load_default_context()
185 self._prepare_and_set_clone_url(c)
165 self._prepare_and_set_clone_url(c)
186 self._load_commits_context(c)
166 self._load_commits_context(c)
187 return self._get_template_context(c)
167 return self._get_template_context(c)
188
168
189 @LoginRequired()
169 @LoginRequired()
190 @HasRepoPermissionAnyDecorator(
170 @HasRepoPermissionAnyDecorator(
191 'repository.read', 'repository.write', 'repository.admin')
171 'repository.read', 'repository.write', 'repository.admin')
192 @view_config(
172 @view_config(
193 route_name='repo_summary', request_method='GET',
173 route_name='repo_summary', request_method='GET',
194 renderer='rhodecode:templates/summary/summary.mako')
174 renderer='rhodecode:templates/summary/summary.mako')
195 @view_config(
175 @view_config(
196 route_name='repo_summary_slash', request_method='GET',
176 route_name='repo_summary_slash', request_method='GET',
197 renderer='rhodecode:templates/summary/summary.mako')
177 renderer='rhodecode:templates/summary/summary.mako')
198 @view_config(
178 @view_config(
199 route_name='repo_summary_explicit', request_method='GET',
179 route_name='repo_summary_explicit', request_method='GET',
200 renderer='rhodecode:templates/summary/summary.mako')
180 renderer='rhodecode:templates/summary/summary.mako')
201 def summary(self):
181 def summary(self):
202 c = self.load_default_context()
182 c = self.load_default_context()
203
183
204 # Prepare the clone URL
184 # Prepare the clone URL
205 self._prepare_and_set_clone_url(c)
185 self._prepare_and_set_clone_url(c)
206
186
207 # update every 5 min
187 # update every 5 min
208 if self.db_repo.last_commit_cache_update_diff > 60 * 5:
188 if self.db_repo.last_commit_cache_update_diff > 60 * 5:
209 self.db_repo.update_commit_cache()
189 self.db_repo.update_commit_cache()
210
190
211 # If enabled, get statistics data
191 # If enabled, get statistics data
212
192
213 c.show_stats = bool(self.db_repo.enable_statistics)
193 c.show_stats = bool(self.db_repo.enable_statistics)
214
194
215 stats = Session().query(Statistics) \
195 stats = Session().query(Statistics) \
216 .filter(Statistics.repository == self.db_repo) \
196 .filter(Statistics.repository == self.db_repo) \
217 .scalar()
197 .scalar()
218
198
219 c.stats_percentage = 0
199 c.stats_percentage = 0
220
200
221 if stats and stats.languages:
201 if stats and stats.languages:
222 c.no_data = False is self.db_repo.enable_statistics
202 c.no_data = False is self.db_repo.enable_statistics
223 lang_stats_d = json.loads(stats.languages)
203 lang_stats_d = json.loads(stats.languages)
224
204
225 # Sort first by decreasing count and second by the file extension,
205 # Sort first by decreasing count and second by the file extension,
226 # so we have a consistent output.
206 # so we have a consistent output.
227 lang_stats_items = sorted(lang_stats_d.iteritems(),
207 lang_stats_items = sorted(lang_stats_d.iteritems(),
228 key=lambda k: (-k[1], k[0]))[:10]
208 key=lambda k: (-k[1], k[0]))[:10]
229 lang_stats = [(x, {"count": y,
209 lang_stats = [(x, {"count": y,
230 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
210 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
231 for x, y in lang_stats_items]
211 for x, y in lang_stats_items]
232
212
233 c.trending_languages = json.dumps(lang_stats)
213 c.trending_languages = json.dumps(lang_stats)
234 else:
214 else:
235 c.no_data = True
215 c.no_data = True
236 c.trending_languages = json.dumps({})
216 c.trending_languages = json.dumps({})
237
217
238 scm_model = ScmModel()
218 scm_model = ScmModel()
239 c.enable_downloads = self.db_repo.enable_downloads
219 c.enable_downloads = self.db_repo.enable_downloads
240 c.repository_followers = scm_model.get_followers(self.db_repo)
220 c.repository_followers = scm_model.get_followers(self.db_repo)
241 c.repository_forks = scm_model.get_forks(self.db_repo)
221 c.repository_forks = scm_model.get_forks(self.db_repo)
242
222
243 # first interaction with the VCS instance after here...
223 # first interaction with the VCS instance after here...
244 if c.repository_requirements_missing:
224 if c.repository_requirements_missing:
245 self.request.override_renderer = \
225 self.request.override_renderer = \
246 'rhodecode:templates/summary/missing_requirements.mako'
226 'rhodecode:templates/summary/missing_requirements.mako'
247 return self._get_template_context(c)
227 return self._get_template_context(c)
248
228
249 c.readme_data, c.readme_file = \
229 c.readme_data, c.readme_file = \
250 self._get_readme_data(self.db_repo, c.visual.default_renderer)
230 self._get_readme_data(self.db_repo, c.visual.default_renderer)
251
231
252 # loads the summary commits template context
232 # loads the summary commits template context
253 self._load_commits_context(c)
233 self._load_commits_context(c)
254
234
255 return self._get_template_context(c)
235 return self._get_template_context(c)
256
236
257 def get_request_commit_id(self):
237 def get_request_commit_id(self):
258 return self.request.matchdict['commit_id']
238 return self.request.matchdict['commit_id']
259
239
260 @LoginRequired()
240 @LoginRequired()
261 @HasRepoPermissionAnyDecorator(
241 @HasRepoPermissionAnyDecorator(
262 'repository.read', 'repository.write', 'repository.admin')
242 'repository.read', 'repository.write', 'repository.admin')
263 @view_config(
243 @view_config(
264 route_name='repo_stats', request_method='GET',
244 route_name='repo_stats', request_method='GET',
265 renderer='json_ext')
245 renderer='json_ext')
266 def repo_stats(self):
246 def repo_stats(self):
267 commit_id = self.get_request_commit_id()
247 commit_id = self.get_request_commit_id()
268 show_stats = bool(self.db_repo.enable_statistics)
248 show_stats = bool(self.db_repo.enable_statistics)
269 repo_id = self.db_repo.repo_id
249 repo_id = self.db_repo.repo_id
270
250
271 cache_seconds = safe_int(
251 cache_seconds = safe_int(
272 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
252 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
273 cache_on = cache_seconds > 0
253 cache_on = cache_seconds > 0
274 log.debug(
254 log.debug(
275 'Computing REPO TREE for repo_id %s commit_id `%s` '
255 'Computing REPO TREE for repo_id %s commit_id `%s` '
276 'with caching: %s[TTL: %ss]' % (
256 'with caching: %s[TTL: %ss]' % (
277 repo_id, commit_id, cache_on, cache_seconds or 0))
257 repo_id, commit_id, cache_on, cache_seconds or 0))
278
258
279 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
259 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
280 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
260 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
281
261
282 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
262 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
283 condition=cache_on)
263 condition=cache_on)
284 def compute_stats(repo_id, commit_id, show_stats):
264 def compute_stats(repo_id, commit_id, show_stats):
285 code_stats = {}
265 code_stats = {}
286 size = 0
266 size = 0
287 try:
267 try:
288 scm_instance = self.db_repo.scm_instance()
268 scm_instance = self.db_repo.scm_instance()
289 commit = scm_instance.get_commit(commit_id)
269 commit = scm_instance.get_commit(commit_id)
290
270
291 for node in commit.get_filenodes_generator():
271 for node in commit.get_filenodes_generator():
292 size += node.size
272 size += node.size
293 if not show_stats:
273 if not show_stats:
294 continue
274 continue
295 ext = string.lower(node.extension)
275 ext = string.lower(node.extension)
296 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
276 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
297 if ext_info:
277 if ext_info:
298 if ext in code_stats:
278 if ext in code_stats:
299 code_stats[ext]['count'] += 1
279 code_stats[ext]['count'] += 1
300 else:
280 else:
301 code_stats[ext] = {"count": 1, "desc": ext_info}
281 code_stats[ext] = {"count": 1, "desc": ext_info}
302 except (EmptyRepositoryError, CommitDoesNotExistError):
282 except (EmptyRepositoryError, CommitDoesNotExistError):
303 pass
283 pass
304 return {'size': h.format_byte_size_binary(size),
284 return {'size': h.format_byte_size_binary(size),
305 'code_stats': code_stats}
285 'code_stats': code_stats}
306
286
307 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
287 stats = compute_stats(self.db_repo.repo_id, commit_id, show_stats)
308 return stats
288 return stats
309
289
310 @LoginRequired()
290 @LoginRequired()
311 @HasRepoPermissionAnyDecorator(
291 @HasRepoPermissionAnyDecorator(
312 'repository.read', 'repository.write', 'repository.admin')
292 'repository.read', 'repository.write', 'repository.admin')
313 @view_config(
293 @view_config(
314 route_name='repo_refs_data', request_method='GET',
294 route_name='repo_refs_data', request_method='GET',
315 renderer='json_ext')
295 renderer='json_ext')
316 def repo_refs_data(self):
296 def repo_refs_data(self):
317 _ = self.request.translate
297 _ = self.request.translate
318 self.load_default_context()
298 self.load_default_context()
319
299
320 repo = self.rhodecode_vcs_repo
300 repo = self.rhodecode_vcs_repo
321 refs_to_create = [
301 refs_to_create = [
322 (_("Branch"), repo.branches, 'branch'),
302 (_("Branch"), repo.branches, 'branch'),
323 (_("Tag"), repo.tags, 'tag'),
303 (_("Tag"), repo.tags, 'tag'),
324 (_("Bookmark"), repo.bookmarks, 'book'),
304 (_("Bookmark"), repo.bookmarks, 'book'),
325 ]
305 ]
326 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
306 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
327 data = {
307 data = {
328 'more': False,
308 'more': False,
329 'results': res
309 'results': res
330 }
310 }
331 return data
311 return data
332
312
333 @LoginRequired()
313 @LoginRequired()
334 @HasRepoPermissionAnyDecorator(
314 @HasRepoPermissionAnyDecorator(
335 'repository.read', 'repository.write', 'repository.admin')
315 'repository.read', 'repository.write', 'repository.admin')
336 @view_config(
316 @view_config(
337 route_name='repo_refs_changelog_data', request_method='GET',
317 route_name='repo_refs_changelog_data', request_method='GET',
338 renderer='json_ext')
318 renderer='json_ext')
339 def repo_refs_changelog_data(self):
319 def repo_refs_changelog_data(self):
340 _ = self.request.translate
320 _ = self.request.translate
341 self.load_default_context()
321 self.load_default_context()
342
322
343 repo = self.rhodecode_vcs_repo
323 repo = self.rhodecode_vcs_repo
344
324
345 refs_to_create = [
325 refs_to_create = [
346 (_("Branches"), repo.branches, 'branch'),
326 (_("Branches"), repo.branches, 'branch'),
347 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
327 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
348 # TODO: enable when vcs can handle bookmarks filters
328 # TODO: enable when vcs can handle bookmarks filters
349 # (_("Bookmarks"), repo.bookmarks, "book"),
329 # (_("Bookmarks"), repo.bookmarks, "book"),
350 ]
330 ]
351 res = self._create_reference_data(
331 res = self._create_reference_data(
352 repo, self.db_repo_name, refs_to_create)
332 repo, self.db_repo_name, refs_to_create)
353 data = {
333 data = {
354 'more': False,
334 'more': False,
355 'results': res
335 'results': res
356 }
336 }
357 return data
337 return data
358
338
359 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
339 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
360 format_ref_id = get_format_ref_id(repo)
340 format_ref_id = get_format_ref_id(repo)
361
341
362 result = []
342 result = []
363 for title, refs, ref_type in refs_to_create:
343 for title, refs, ref_type in refs_to_create:
364 if refs:
344 if refs:
365 result.append({
345 result.append({
366 'text': title,
346 'text': title,
367 'children': self._create_reference_items(
347 'children': self._create_reference_items(
368 repo, full_repo_name, refs, ref_type,
348 repo, full_repo_name, refs, ref_type,
369 format_ref_id),
349 format_ref_id),
370 })
350 })
371 return result
351 return result
372
352
373 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
353 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
374 result = []
354 result = []
375 is_svn = h.is_svn(repo)
355 is_svn = h.is_svn(repo)
376 for ref_name, raw_id in refs.iteritems():
356 for ref_name, raw_id in refs.iteritems():
377 files_url = self._create_files_url(
357 files_url = self._create_files_url(
378 repo, full_repo_name, ref_name, raw_id, is_svn)
358 repo, full_repo_name, ref_name, raw_id, is_svn)
379 result.append({
359 result.append({
380 'text': ref_name,
360 'text': ref_name,
381 'id': format_ref_id(ref_name, raw_id),
361 'id': format_ref_id(ref_name, raw_id),
382 'raw_id': raw_id,
362 'raw_id': raw_id,
383 'type': ref_type,
363 'type': ref_type,
384 'files_url': files_url,
364 'files_url': files_url,
385 'idx': 0,
365 'idx': 0,
386 })
366 })
387 return result
367 return result
388
368
389 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
369 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
390 use_commit_id = '/' in ref_name or is_svn
370 use_commit_id = '/' in ref_name or is_svn
391 return h.route_path(
371 return h.route_path(
392 'repo_files',
372 'repo_files',
393 repo_name=full_repo_name,
373 repo_name=full_repo_name,
394 f_path=ref_name if is_svn else '',
374 f_path=ref_name if is_svn else '',
395 commit_id=raw_id if use_commit_id else ref_name,
375 commit_id=raw_id if use_commit_id else ref_name,
396 _query=dict(at=ref_name))
376 _query=dict(at=ref_name))
@@ -1,340 +1,343 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2019 RhodeCode GmbH
3 # Copyright (C) 2012-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 """
21 """
22 RhodeCode task modules, containing all task that suppose to be run
22 RhodeCode task modules, containing all task that suppose to be run
23 by celery daemon
23 by celery daemon
24 """
24 """
25
25
26 import os
26 import os
27 import time
27 import time
28
28
29 from pyramid import compat
29 from pyramid import compat
30 from pyramid_mailer.mailer import Mailer
30 from pyramid_mailer.mailer import Mailer
31 from pyramid_mailer.message import Message
31 from pyramid_mailer.message import Message
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask
35 from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask
36 from rhodecode.lib.hooks_base import log_create_repository
36 from rhodecode.lib.hooks_base import log_create_repository
37 from rhodecode.lib.utils2 import safe_int, str2bool
37 from rhodecode.lib.utils2 import safe_int, str2bool
38 from rhodecode.model.db import Session, IntegrityError, Repository, User, true
38 from rhodecode.model.db import Session, IntegrityError, Repository, User, true
39
39
40
40
41 @async_task(ignore_result=True, base=RequestContextTask)
41 @async_task(ignore_result=True, base=RequestContextTask)
42 def send_email(recipients, subject, body='', html_body='', email_config=None):
42 def send_email(recipients, subject, body='', html_body='', email_config=None):
43 """
43 """
44 Sends an email with defined parameters from the .ini files.
44 Sends an email with defined parameters from the .ini files.
45
45
46 :param recipients: list of recipients, it this is empty the defined email
46 :param recipients: list of recipients, it this is empty the defined email
47 address from field 'email_to' is used instead
47 address from field 'email_to' is used instead
48 :param subject: subject of the mail
48 :param subject: subject of the mail
49 :param body: body of the mail
49 :param body: body of the mail
50 :param html_body: html version of body
50 :param html_body: html version of body
51 """
51 """
52 log = get_logger(send_email)
52 log = get_logger(send_email)
53
53
54 email_config = email_config or rhodecode.CONFIG
54 email_config = email_config or rhodecode.CONFIG
55
55
56 mail_server = email_config.get('smtp_server') or None
56 mail_server = email_config.get('smtp_server') or None
57 if mail_server is None:
57 if mail_server is None:
58 log.error("SMTP server information missing. Sending email failed. "
58 log.error("SMTP server information missing. Sending email failed. "
59 "Make sure that `smtp_server` variable is configured "
59 "Make sure that `smtp_server` variable is configured "
60 "inside the .ini file")
60 "inside the .ini file")
61 return False
61 return False
62
62
63 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
63 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
64
64
65 if recipients:
65 if recipients:
66 if isinstance(recipients, compat.string_types):
66 if isinstance(recipients, compat.string_types):
67 recipients = recipients.split(',')
67 recipients = recipients.split(',')
68 else:
68 else:
69 # if recipients are not defined we send to email_config + all admins
69 # if recipients are not defined we send to email_config + all admins
70 admins = []
70 admins = []
71 for u in User.query().filter(User.admin == true()).all():
71 for u in User.query().filter(User.admin == true()).all():
72 if u.email:
72 if u.email:
73 admins.append(u.email)
73 admins.append(u.email)
74 recipients = []
74 recipients = []
75 config_email = email_config.get('email_to')
75 config_email = email_config.get('email_to')
76 if config_email:
76 if config_email:
77 recipients += [config_email]
77 recipients += [config_email]
78 recipients += admins
78 recipients += admins
79
79
80 # translate our LEGACY config into the one that pyramid_mailer supports
80 # translate our LEGACY config into the one that pyramid_mailer supports
81 email_conf = dict(
81 email_conf = dict(
82 host=mail_server,
82 host=mail_server,
83 port=email_config.get('smtp_port', 25),
83 port=email_config.get('smtp_port', 25),
84 username=email_config.get('smtp_username'),
84 username=email_config.get('smtp_username'),
85 password=email_config.get('smtp_password'),
85 password=email_config.get('smtp_password'),
86
86
87 tls=str2bool(email_config.get('smtp_use_tls')),
87 tls=str2bool(email_config.get('smtp_use_tls')),
88 ssl=str2bool(email_config.get('smtp_use_ssl')),
88 ssl=str2bool(email_config.get('smtp_use_ssl')),
89
89
90 # SSL key file
90 # SSL key file
91 # keyfile='',
91 # keyfile='',
92
92
93 # SSL certificate file
93 # SSL certificate file
94 # certfile='',
94 # certfile='',
95
95
96 # Location of maildir
96 # Location of maildir
97 # queue_path='',
97 # queue_path='',
98
98
99 default_sender=email_config.get('app_email_from', 'RhodeCode'),
99 default_sender=email_config.get('app_email_from', 'RhodeCode'),
100
100
101 debug=str2bool(email_config.get('smtp_debug')),
101 debug=str2bool(email_config.get('smtp_debug')),
102 # /usr/sbin/sendmail Sendmail executable
102 # /usr/sbin/sendmail Sendmail executable
103 # sendmail_app='',
103 # sendmail_app='',
104
104
105 # {sendmail_app} -t -i -f {sender} Template for sendmail execution
105 # {sendmail_app} -t -i -f {sender} Template for sendmail execution
106 # sendmail_template='',
106 # sendmail_template='',
107 )
107 )
108
108
109 try:
109 try:
110 mailer = Mailer(**email_conf)
110 mailer = Mailer(**email_conf)
111
111
112 message = Message(subject=subject,
112 message = Message(subject=subject,
113 sender=email_conf['default_sender'],
113 sender=email_conf['default_sender'],
114 recipients=recipients,
114 recipients=recipients,
115 body=body, html=html_body)
115 body=body, html=html_body)
116 mailer.send_immediately(message)
116 mailer.send_immediately(message)
117
117
118 except Exception:
118 except Exception:
119 log.exception('Mail sending failed')
119 log.exception('Mail sending failed')
120 return False
120 return False
121 return True
121 return True
122
122
123
123
124 @async_task(ignore_result=True, base=RequestContextTask)
124 @async_task(ignore_result=True, base=RequestContextTask)
125 def create_repo(form_data, cur_user):
125 def create_repo(form_data, cur_user):
126 from rhodecode.model.repo import RepoModel
126 from rhodecode.model.repo import RepoModel
127 from rhodecode.model.user import UserModel
127 from rhodecode.model.user import UserModel
128 from rhodecode.model.scm import ScmModel
128 from rhodecode.model.settings import SettingsModel
129 from rhodecode.model.settings import SettingsModel
129
130
130 log = get_logger(create_repo)
131 log = get_logger(create_repo)
131
132
132 cur_user = UserModel()._get_user(cur_user)
133 cur_user = UserModel()._get_user(cur_user)
133 owner = cur_user
134 owner = cur_user
134
135
135 repo_name = form_data['repo_name']
136 repo_name = form_data['repo_name']
136 repo_name_full = form_data['repo_name_full']
137 repo_name_full = form_data['repo_name_full']
137 repo_type = form_data['repo_type']
138 repo_type = form_data['repo_type']
138 description = form_data['repo_description']
139 description = form_data['repo_description']
139 private = form_data['repo_private']
140 private = form_data['repo_private']
140 clone_uri = form_data.get('clone_uri')
141 clone_uri = form_data.get('clone_uri')
141 repo_group = safe_int(form_data['repo_group'])
142 repo_group = safe_int(form_data['repo_group'])
142 landing_rev = form_data['repo_landing_rev']
143 copy_fork_permissions = form_data.get('copy_permissions')
143 copy_fork_permissions = form_data.get('copy_permissions')
144 copy_group_permissions = form_data.get('repo_copy_permissions')
144 copy_group_permissions = form_data.get('repo_copy_permissions')
145 fork_of = form_data.get('fork_parent_id')
145 fork_of = form_data.get('fork_parent_id')
146 state = form_data.get('repo_state', Repository.STATE_PENDING)
146 state = form_data.get('repo_state', Repository.STATE_PENDING)
147
147
148 # repo creation defaults, private and repo_type are filled in form
148 # repo creation defaults, private and repo_type are filled in form
149 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
149 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
150 enable_statistics = form_data.get(
150 enable_statistics = form_data.get(
151 'enable_statistics', defs.get('repo_enable_statistics'))
151 'enable_statistics', defs.get('repo_enable_statistics'))
152 enable_locking = form_data.get(
152 enable_locking = form_data.get(
153 'enable_locking', defs.get('repo_enable_locking'))
153 'enable_locking', defs.get('repo_enable_locking'))
154 enable_downloads = form_data.get(
154 enable_downloads = form_data.get(
155 'enable_downloads', defs.get('repo_enable_downloads'))
155 'enable_downloads', defs.get('repo_enable_downloads'))
156
156
157 # set landing rev based on default branches for SCM
158 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
159
157 try:
160 try:
158 RepoModel()._create_repo(
161 RepoModel()._create_repo(
159 repo_name=repo_name_full,
162 repo_name=repo_name_full,
160 repo_type=repo_type,
163 repo_type=repo_type,
161 description=description,
164 description=description,
162 owner=owner,
165 owner=owner,
163 private=private,
166 private=private,
164 clone_uri=clone_uri,
167 clone_uri=clone_uri,
165 repo_group=repo_group,
168 repo_group=repo_group,
166 landing_rev=landing_rev,
169 landing_rev=landing_ref,
167 fork_of=fork_of,
170 fork_of=fork_of,
168 copy_fork_permissions=copy_fork_permissions,
171 copy_fork_permissions=copy_fork_permissions,
169 copy_group_permissions=copy_group_permissions,
172 copy_group_permissions=copy_group_permissions,
170 enable_statistics=enable_statistics,
173 enable_statistics=enable_statistics,
171 enable_locking=enable_locking,
174 enable_locking=enable_locking,
172 enable_downloads=enable_downloads,
175 enable_downloads=enable_downloads,
173 state=state
176 state=state
174 )
177 )
175 Session().commit()
178 Session().commit()
176
179
177 # now create this repo on Filesystem
180 # now create this repo on Filesystem
178 RepoModel()._create_filesystem_repo(
181 RepoModel()._create_filesystem_repo(
179 repo_name=repo_name,
182 repo_name=repo_name,
180 repo_type=repo_type,
183 repo_type=repo_type,
181 repo_group=RepoModel()._get_repo_group(repo_group),
184 repo_group=RepoModel()._get_repo_group(repo_group),
182 clone_uri=clone_uri,
185 clone_uri=clone_uri,
183 )
186 )
184 repo = Repository.get_by_repo_name(repo_name_full)
187 repo = Repository.get_by_repo_name(repo_name_full)
185 log_create_repository(created_by=owner.username, **repo.get_dict())
188 log_create_repository(created_by=owner.username, **repo.get_dict())
186
189
187 # update repo commit caches initially
190 # update repo commit caches initially
188 repo.update_commit_cache()
191 repo.update_commit_cache()
189
192
190 # set new created state
193 # set new created state
191 repo.set_state(Repository.STATE_CREATED)
194 repo.set_state(Repository.STATE_CREATED)
192 repo_id = repo.repo_id
195 repo_id = repo.repo_id
193 repo_data = repo.get_api_data()
196 repo_data = repo.get_api_data()
194
197
195 audit_logger.store(
198 audit_logger.store(
196 'repo.create', action_data={'data': repo_data},
199 'repo.create', action_data={'data': repo_data},
197 user=cur_user,
200 user=cur_user,
198 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
201 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
199
202
200 Session().commit()
203 Session().commit()
201 except Exception as e:
204 except Exception as e:
202 log.warning('Exception occurred when creating repository, '
205 log.warning('Exception occurred when creating repository, '
203 'doing cleanup...', exc_info=True)
206 'doing cleanup...', exc_info=True)
204 if isinstance(e, IntegrityError):
207 if isinstance(e, IntegrityError):
205 Session().rollback()
208 Session().rollback()
206
209
207 # rollback things manually !
210 # rollback things manually !
208 repo = Repository.get_by_repo_name(repo_name_full)
211 repo = Repository.get_by_repo_name(repo_name_full)
209 if repo:
212 if repo:
210 Repository.delete(repo.repo_id)
213 Repository.delete(repo.repo_id)
211 Session().commit()
214 Session().commit()
212 RepoModel()._delete_filesystem_repo(repo)
215 RepoModel()._delete_filesystem_repo(repo)
213 log.info('Cleanup of repo %s finished', repo_name_full)
216 log.info('Cleanup of repo %s finished', repo_name_full)
214 raise
217 raise
215
218
216 return True
219 return True
217
220
218
221
219 @async_task(ignore_result=True, base=RequestContextTask)
222 @async_task(ignore_result=True, base=RequestContextTask)
220 def create_repo_fork(form_data, cur_user):
223 def create_repo_fork(form_data, cur_user):
221 """
224 """
222 Creates a fork of repository using internal VCS methods
225 Creates a fork of repository using internal VCS methods
223 """
226 """
224 from rhodecode.model.repo import RepoModel
227 from rhodecode.model.repo import RepoModel
225 from rhodecode.model.user import UserModel
228 from rhodecode.model.user import UserModel
226
229
227 log = get_logger(create_repo_fork)
230 log = get_logger(create_repo_fork)
228
231
229 cur_user = UserModel()._get_user(cur_user)
232 cur_user = UserModel()._get_user(cur_user)
230 owner = cur_user
233 owner = cur_user
231
234
232 repo_name = form_data['repo_name'] # fork in this case
235 repo_name = form_data['repo_name'] # fork in this case
233 repo_name_full = form_data['repo_name_full']
236 repo_name_full = form_data['repo_name_full']
234 repo_type = form_data['repo_type']
237 repo_type = form_data['repo_type']
235 description = form_data['description']
238 description = form_data['description']
236 private = form_data['private']
239 private = form_data['private']
237 clone_uri = form_data.get('clone_uri')
240 clone_uri = form_data.get('clone_uri')
238 repo_group = safe_int(form_data['repo_group'])
241 repo_group = safe_int(form_data['repo_group'])
239 landing_rev = form_data['landing_rev']
242 landing_rev = form_data['landing_rev']
240 copy_fork_permissions = form_data.get('copy_permissions')
243 copy_fork_permissions = form_data.get('copy_permissions')
241 fork_id = safe_int(form_data.get('fork_parent_id'))
244 fork_id = safe_int(form_data.get('fork_parent_id'))
242
245
243 try:
246 try:
244 fork_of = RepoModel()._get_repo(fork_id)
247 fork_of = RepoModel()._get_repo(fork_id)
245 RepoModel()._create_repo(
248 RepoModel()._create_repo(
246 repo_name=repo_name_full,
249 repo_name=repo_name_full,
247 repo_type=repo_type,
250 repo_type=repo_type,
248 description=description,
251 description=description,
249 owner=owner,
252 owner=owner,
250 private=private,
253 private=private,
251 clone_uri=clone_uri,
254 clone_uri=clone_uri,
252 repo_group=repo_group,
255 repo_group=repo_group,
253 landing_rev=landing_rev,
256 landing_rev=landing_rev,
254 fork_of=fork_of,
257 fork_of=fork_of,
255 copy_fork_permissions=copy_fork_permissions
258 copy_fork_permissions=copy_fork_permissions
256 )
259 )
257
260
258 Session().commit()
261 Session().commit()
259
262
260 base_path = Repository.base_path()
263 base_path = Repository.base_path()
261 source_repo_path = os.path.join(base_path, fork_of.repo_name)
264 source_repo_path = os.path.join(base_path, fork_of.repo_name)
262
265
263 # now create this repo on Filesystem
266 # now create this repo on Filesystem
264 RepoModel()._create_filesystem_repo(
267 RepoModel()._create_filesystem_repo(
265 repo_name=repo_name,
268 repo_name=repo_name,
266 repo_type=repo_type,
269 repo_type=repo_type,
267 repo_group=RepoModel()._get_repo_group(repo_group),
270 repo_group=RepoModel()._get_repo_group(repo_group),
268 clone_uri=source_repo_path,
271 clone_uri=source_repo_path,
269 )
272 )
270 repo = Repository.get_by_repo_name(repo_name_full)
273 repo = Repository.get_by_repo_name(repo_name_full)
271 log_create_repository(created_by=owner.username, **repo.get_dict())
274 log_create_repository(created_by=owner.username, **repo.get_dict())
272
275
273 # update repo commit caches initially
276 # update repo commit caches initially
274 config = repo._config
277 config = repo._config
275 config.set('extensions', 'largefiles', '')
278 config.set('extensions', 'largefiles', '')
276 repo.update_commit_cache(config=config)
279 repo.update_commit_cache(config=config)
277
280
278 # set new created state
281 # set new created state
279 repo.set_state(Repository.STATE_CREATED)
282 repo.set_state(Repository.STATE_CREATED)
280
283
281 repo_id = repo.repo_id
284 repo_id = repo.repo_id
282 repo_data = repo.get_api_data()
285 repo_data = repo.get_api_data()
283 audit_logger.store(
286 audit_logger.store(
284 'repo.fork', action_data={'data': repo_data},
287 'repo.fork', action_data={'data': repo_data},
285 user=cur_user,
288 user=cur_user,
286 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
289 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
287
290
288 Session().commit()
291 Session().commit()
289 except Exception as e:
292 except Exception as e:
290 log.warning('Exception occurred when forking repository, '
293 log.warning('Exception occurred when forking repository, '
291 'doing cleanup...', exc_info=True)
294 'doing cleanup...', exc_info=True)
292 if isinstance(e, IntegrityError):
295 if isinstance(e, IntegrityError):
293 Session().rollback()
296 Session().rollback()
294
297
295 # rollback things manually !
298 # rollback things manually !
296 repo = Repository.get_by_repo_name(repo_name_full)
299 repo = Repository.get_by_repo_name(repo_name_full)
297 if repo:
300 if repo:
298 Repository.delete(repo.repo_id)
301 Repository.delete(repo.repo_id)
299 Session().commit()
302 Session().commit()
300 RepoModel()._delete_filesystem_repo(repo)
303 RepoModel()._delete_filesystem_repo(repo)
301 log.info('Cleanup of repo %s finished', repo_name_full)
304 log.info('Cleanup of repo %s finished', repo_name_full)
302 raise
305 raise
303
306
304 return True
307 return True
305
308
306
309
307 @async_task(ignore_result=True)
310 @async_task(ignore_result=True)
308 def repo_maintenance(repoid):
311 def repo_maintenance(repoid):
309 from rhodecode.lib import repo_maintenance as repo_maintenance_lib
312 from rhodecode.lib import repo_maintenance as repo_maintenance_lib
310 log = get_logger(repo_maintenance)
313 log = get_logger(repo_maintenance)
311 repo = Repository.get_by_id_or_repo_name(repoid)
314 repo = Repository.get_by_id_or_repo_name(repoid)
312 if repo:
315 if repo:
313 maintenance = repo_maintenance_lib.RepoMaintenance()
316 maintenance = repo_maintenance_lib.RepoMaintenance()
314 tasks = maintenance.get_tasks_for_repo(repo)
317 tasks = maintenance.get_tasks_for_repo(repo)
315 log.debug('Executing %s tasks on repo `%s`', tasks, repoid)
318 log.debug('Executing %s tasks on repo `%s`', tasks, repoid)
316 executed_types = maintenance.execute(repo)
319 executed_types = maintenance.execute(repo)
317 log.debug('Got execution results %s', executed_types)
320 log.debug('Got execution results %s', executed_types)
318 else:
321 else:
319 log.debug('Repo `%s` not found or without a clone_url', repoid)
322 log.debug('Repo `%s` not found or without a clone_url', repoid)
320
323
321
324
322 @async_task(ignore_result=True)
325 @async_task(ignore_result=True)
323 def check_for_update():
326 def check_for_update():
324 from rhodecode.model.update import UpdateModel
327 from rhodecode.model.update import UpdateModel
325 update_url = UpdateModel().get_update_url()
328 update_url = UpdateModel().get_update_url()
326 cur_ver = rhodecode.__version__
329 cur_ver = rhodecode.__version__
327
330
328 try:
331 try:
329 data = UpdateModel().get_update_data(update_url)
332 data = UpdateModel().get_update_data(update_url)
330 latest = data['versions'][0]
333 latest = data['versions'][0]
331 UpdateModel().store_version(latest['version'])
334 UpdateModel().store_version(latest['version'])
332 except Exception:
335 except Exception:
333 pass
336 pass
334
337
335
338
336 @async_task(ignore_result=False)
339 @async_task(ignore_result=False)
337 def beat_check(*args, **kwargs):
340 def beat_check(*args, **kwargs):
338 log = get_logger(beat_check)
341 log = get_logger(beat_check)
339 log.info('Got args: %r and kwargs %r', args, kwargs)
342 log.info('Got args: %r and kwargs %r', args, kwargs)
340 return time.time()
343 return time.time()
@@ -1,635 +1,629 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-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 """
21 """
22 this is forms validation classes
22 this is forms validation classes
23 http://formencode.org/module-formencode.validators.html
23 http://formencode.org/module-formencode.validators.html
24 for list off all availible validators
24 for list off all availible validators
25
25
26 we can create our own validators
26 we can create our own validators
27
27
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 pre_validators [] These validators will be applied before the schema
29 pre_validators [] These validators will be applied before the schema
30 chained_validators [] These validators will be applied after the schema
30 chained_validators [] These validators will be applied after the schema
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35
35
36
36
37 <name> = formencode.validators.<name of validator>
37 <name> = formencode.validators.<name of validator>
38 <name> must equal form name
38 <name> must equal form name
39 list=[1,2,3,4,5]
39 list=[1,2,3,4,5]
40 for SELECT use formencode.All(OneOf(list), Int())
40 for SELECT use formencode.All(OneOf(list), Int())
41
41
42 """
42 """
43
43
44 import deform
44 import deform
45 import logging
45 import logging
46 import formencode
46 import formencode
47
47
48 from pkg_resources import resource_filename
48 from pkg_resources import resource_filename
49 from formencode import All, Pipe
49 from formencode import All, Pipe
50
50
51 from pyramid.threadlocal import get_current_request
51 from pyramid.threadlocal import get_current_request
52
52
53 from rhodecode import BACKENDS
53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
54 from rhodecode.lib import helpers
55 from rhodecode.model import validators as v
55 from rhodecode.model import validators as v
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 deform_templates = resource_filename('deform', 'templates')
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
62 search_path = (rhodecode_templates, deform_templates)
63
63
64
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
68 kw['h'] = helpers
69 kw['request'] = get_current_request()
69 kw['request'] = get_current_request()
70 return self.load(template_name)(**kw)
70 return self.load(template_name)(**kw)
71
71
72
72
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
74 deform.Form.set_default_renderer(form_renderer)
74 deform.Form.set_default_renderer(form_renderer)
75
75
76
76
77 def LoginForm(localizer):
77 def LoginForm(localizer):
78 _ = localizer
78 _ = localizer
79
79
80 class _LoginForm(formencode.Schema):
80 class _LoginForm(formencode.Schema):
81 allow_extra_fields = True
81 allow_extra_fields = True
82 filter_extra_fields = True
82 filter_extra_fields = True
83 username = v.UnicodeString(
83 username = v.UnicodeString(
84 strip=True,
84 strip=True,
85 min=1,
85 min=1,
86 not_empty=True,
86 not_empty=True,
87 messages={
87 messages={
88 'empty': _(u'Please enter a login'),
88 'empty': _(u'Please enter a login'),
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
90 }
90 }
91 )
91 )
92
92
93 password = v.UnicodeString(
93 password = v.UnicodeString(
94 strip=False,
94 strip=False,
95 min=3,
95 min=3,
96 max=72,
96 max=72,
97 not_empty=True,
97 not_empty=True,
98 messages={
98 messages={
99 'empty': _(u'Please enter a password'),
99 'empty': _(u'Please enter a password'),
100 'tooShort': _(u'Enter %(min)i characters or more')}
100 'tooShort': _(u'Enter %(min)i characters or more')}
101 )
101 )
102
102
103 remember = v.StringBoolean(if_missing=False)
103 remember = v.StringBoolean(if_missing=False)
104
104
105 chained_validators = [v.ValidAuth(localizer)]
105 chained_validators = [v.ValidAuth(localizer)]
106 return _LoginForm
106 return _LoginForm
107
107
108
108
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
110 old_data = old_data or {}
110 old_data = old_data or {}
111 available_languages = available_languages or []
111 available_languages = available_languages or []
112 _ = localizer
112 _ = localizer
113
113
114 class _UserForm(formencode.Schema):
114 class _UserForm(formencode.Schema):
115 allow_extra_fields = True
115 allow_extra_fields = True
116 filter_extra_fields = True
116 filter_extra_fields = True
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
118 v.ValidUsername(localizer, edit, old_data))
118 v.ValidUsername(localizer, edit, old_data))
119 if edit:
119 if edit:
120 new_password = All(
120 new_password = All(
121 v.ValidPassword(localizer),
121 v.ValidPassword(localizer),
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
123 )
123 )
124 password_confirmation = All(
124 password_confirmation = All(
125 v.ValidPassword(localizer),
125 v.ValidPassword(localizer),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
127 )
127 )
128 admin = v.StringBoolean(if_missing=False)
128 admin = v.StringBoolean(if_missing=False)
129 else:
129 else:
130 password = All(
130 password = All(
131 v.ValidPassword(localizer),
131 v.ValidPassword(localizer),
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
133 )
133 )
134 password_confirmation = All(
134 password_confirmation = All(
135 v.ValidPassword(localizer),
135 v.ValidPassword(localizer),
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
137 )
137 )
138
138
139 password_change = v.StringBoolean(if_missing=False)
139 password_change = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
141
141
142 active = v.StringBoolean(if_missing=False)
142 active = v.StringBoolean(if_missing=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
145 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
146 extern_name = v.UnicodeString(strip=True)
146 extern_name = v.UnicodeString(strip=True)
147 extern_type = v.UnicodeString(strip=True)
147 extern_type = v.UnicodeString(strip=True)
148 language = v.OneOf(available_languages, hideList=False,
148 language = v.OneOf(available_languages, hideList=False,
149 testValueList=True, if_missing=None)
149 testValueList=True, if_missing=None)
150 chained_validators = [v.ValidPasswordsMatch(localizer)]
150 chained_validators = [v.ValidPasswordsMatch(localizer)]
151 return _UserForm
151 return _UserForm
152
152
153
153
154 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
154 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
155 old_data = old_data or {}
155 old_data = old_data or {}
156 _ = localizer
156 _ = localizer
157
157
158 class _UserGroupForm(formencode.Schema):
158 class _UserGroupForm(formencode.Schema):
159 allow_extra_fields = True
159 allow_extra_fields = True
160 filter_extra_fields = True
160 filter_extra_fields = True
161
161
162 users_group_name = All(
162 users_group_name = All(
163 v.UnicodeString(strip=True, min=1, not_empty=True),
163 v.UnicodeString(strip=True, min=1, not_empty=True),
164 v.ValidUserGroup(localizer, edit, old_data)
164 v.ValidUserGroup(localizer, edit, old_data)
165 )
165 )
166 user_group_description = v.UnicodeString(strip=True, min=1,
166 user_group_description = v.UnicodeString(strip=True, min=1,
167 not_empty=False)
167 not_empty=False)
168
168
169 users_group_active = v.StringBoolean(if_missing=False)
169 users_group_active = v.StringBoolean(if_missing=False)
170
170
171 if edit:
171 if edit:
172 # this is user group owner
172 # this is user group owner
173 user = All(
173 user = All(
174 v.UnicodeString(not_empty=True),
174 v.UnicodeString(not_empty=True),
175 v.ValidRepoUser(localizer, allow_disabled))
175 v.ValidRepoUser(localizer, allow_disabled))
176 return _UserGroupForm
176 return _UserGroupForm
177
177
178
178
179 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
179 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
180 can_create_in_root=False, allow_disabled=False):
180 can_create_in_root=False, allow_disabled=False):
181 _ = localizer
181 _ = localizer
182 old_data = old_data or {}
182 old_data = old_data or {}
183 available_groups = available_groups or []
183 available_groups = available_groups or []
184
184
185 class _RepoGroupForm(formencode.Schema):
185 class _RepoGroupForm(formencode.Schema):
186 allow_extra_fields = True
186 allow_extra_fields = True
187 filter_extra_fields = False
187 filter_extra_fields = False
188
188
189 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
189 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
190 v.SlugifyName(localizer),)
190 v.SlugifyName(localizer),)
191 group_description = v.UnicodeString(strip=True, min=1,
191 group_description = v.UnicodeString(strip=True, min=1,
192 not_empty=False)
192 not_empty=False)
193 group_copy_permissions = v.StringBoolean(if_missing=False)
193 group_copy_permissions = v.StringBoolean(if_missing=False)
194
194
195 group_parent_id = v.OneOf(available_groups, hideList=False,
195 group_parent_id = v.OneOf(available_groups, hideList=False,
196 testValueList=True, not_empty=True)
196 testValueList=True, not_empty=True)
197 enable_locking = v.StringBoolean(if_missing=False)
197 enable_locking = v.StringBoolean(if_missing=False)
198 chained_validators = [
198 chained_validators = [
199 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
199 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
200
200
201 if edit:
201 if edit:
202 # this is repo group owner
202 # this is repo group owner
203 user = All(
203 user = All(
204 v.UnicodeString(not_empty=True),
204 v.UnicodeString(not_empty=True),
205 v.ValidRepoUser(localizer, allow_disabled))
205 v.ValidRepoUser(localizer, allow_disabled))
206 return _RepoGroupForm
206 return _RepoGroupForm
207
207
208
208
209 def RegisterForm(localizer, edit=False, old_data=None):
209 def RegisterForm(localizer, edit=False, old_data=None):
210 _ = localizer
210 _ = localizer
211 old_data = old_data or {}
211 old_data = old_data or {}
212
212
213 class _RegisterForm(formencode.Schema):
213 class _RegisterForm(formencode.Schema):
214 allow_extra_fields = True
214 allow_extra_fields = True
215 filter_extra_fields = True
215 filter_extra_fields = True
216 username = All(
216 username = All(
217 v.ValidUsername(localizer, edit, old_data),
217 v.ValidUsername(localizer, edit, old_data),
218 v.UnicodeString(strip=True, min=1, not_empty=True)
218 v.UnicodeString(strip=True, min=1, not_empty=True)
219 )
219 )
220 password = All(
220 password = All(
221 v.ValidPassword(localizer),
221 v.ValidPassword(localizer),
222 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
222 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
223 )
223 )
224 password_confirmation = All(
224 password_confirmation = All(
225 v.ValidPassword(localizer),
225 v.ValidPassword(localizer),
226 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
226 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
227 )
227 )
228 active = v.StringBoolean(if_missing=False)
228 active = v.StringBoolean(if_missing=False)
229 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
229 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
231 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
231 email = All(v.UniqSystemEmail(localizer, old_data), v.Email(not_empty=True))
232
232
233 chained_validators = [v.ValidPasswordsMatch(localizer)]
233 chained_validators = [v.ValidPasswordsMatch(localizer)]
234 return _RegisterForm
234 return _RegisterForm
235
235
236
236
237 def PasswordResetForm(localizer):
237 def PasswordResetForm(localizer):
238 _ = localizer
238 _ = localizer
239
239
240 class _PasswordResetForm(formencode.Schema):
240 class _PasswordResetForm(formencode.Schema):
241 allow_extra_fields = True
241 allow_extra_fields = True
242 filter_extra_fields = True
242 filter_extra_fields = True
243 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
243 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
244 return _PasswordResetForm
244 return _PasswordResetForm
245
245
246
246
247 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None,
247 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None, allow_disabled=False):
248 landing_revs=None, allow_disabled=False):
249 _ = localizer
248 _ = localizer
250 old_data = old_data or {}
249 old_data = old_data or {}
251 repo_groups = repo_groups or []
250 repo_groups = repo_groups or []
252 landing_revs = landing_revs or []
253 supported_backends = BACKENDS.keys()
251 supported_backends = BACKENDS.keys()
254
252
255 class _RepoForm(formencode.Schema):
253 class _RepoForm(formencode.Schema):
256 allow_extra_fields = True
254 allow_extra_fields = True
257 filter_extra_fields = False
255 filter_extra_fields = False
258 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
256 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
259 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
257 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
260 repo_group = All(v.CanWriteGroup(localizer, old_data),
258 repo_group = All(v.CanWriteGroup(localizer, old_data),
261 v.OneOf(repo_groups, hideList=True))
259 v.OneOf(repo_groups, hideList=True))
262 repo_type = v.OneOf(supported_backends, required=False,
260 repo_type = v.OneOf(supported_backends, required=False,
263 if_missing=old_data.get('repo_type'))
261 if_missing=old_data.get('repo_type'))
264 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
262 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
265 repo_private = v.StringBoolean(if_missing=False)
263 repo_private = v.StringBoolean(if_missing=False)
266 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
267 repo_copy_permissions = v.StringBoolean(if_missing=False)
264 repo_copy_permissions = v.StringBoolean(if_missing=False)
268 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
265 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
269
266
270 repo_enable_statistics = v.StringBoolean(if_missing=False)
267 repo_enable_statistics = v.StringBoolean(if_missing=False)
271 repo_enable_downloads = v.StringBoolean(if_missing=False)
268 repo_enable_downloads = v.StringBoolean(if_missing=False)
272 repo_enable_locking = v.StringBoolean(if_missing=False)
269 repo_enable_locking = v.StringBoolean(if_missing=False)
273
270
274 if edit:
271 if edit:
275 # this is repo owner
272 # this is repo owner
276 user = All(
273 user = All(
277 v.UnicodeString(not_empty=True),
274 v.UnicodeString(not_empty=True),
278 v.ValidRepoUser(localizer, allow_disabled))
275 v.ValidRepoUser(localizer, allow_disabled))
279 clone_uri_change = v.UnicodeString(
276 clone_uri_change = v.UnicodeString(
280 not_empty=False, if_missing=v.Missing)
277 not_empty=False, if_missing=v.Missing)
281
278
282 chained_validators = [v.ValidCloneUri(localizer),
279 chained_validators = [v.ValidCloneUri(localizer),
283 v.ValidRepoName(localizer, edit, old_data)]
280 v.ValidRepoName(localizer, edit, old_data)]
284 return _RepoForm
281 return _RepoForm
285
282
286
283
287 def RepoPermsForm(localizer):
284 def RepoPermsForm(localizer):
288 _ = localizer
285 _ = localizer
289
286
290 class _RepoPermsForm(formencode.Schema):
287 class _RepoPermsForm(formencode.Schema):
291 allow_extra_fields = True
288 allow_extra_fields = True
292 filter_extra_fields = False
289 filter_extra_fields = False
293 chained_validators = [v.ValidPerms(localizer, type_='repo')]
290 chained_validators = [v.ValidPerms(localizer, type_='repo')]
294 return _RepoPermsForm
291 return _RepoPermsForm
295
292
296
293
297 def RepoGroupPermsForm(localizer, valid_recursive_choices):
294 def RepoGroupPermsForm(localizer, valid_recursive_choices):
298 _ = localizer
295 _ = localizer
299
296
300 class _RepoGroupPermsForm(formencode.Schema):
297 class _RepoGroupPermsForm(formencode.Schema):
301 allow_extra_fields = True
298 allow_extra_fields = True
302 filter_extra_fields = False
299 filter_extra_fields = False
303 recursive = v.OneOf(valid_recursive_choices)
300 recursive = v.OneOf(valid_recursive_choices)
304 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
301 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
305 return _RepoGroupPermsForm
302 return _RepoGroupPermsForm
306
303
307
304
308 def UserGroupPermsForm(localizer):
305 def UserGroupPermsForm(localizer):
309 _ = localizer
306 _ = localizer
310
307
311 class _UserPermsForm(formencode.Schema):
308 class _UserPermsForm(formencode.Schema):
312 allow_extra_fields = True
309 allow_extra_fields = True
313 filter_extra_fields = False
310 filter_extra_fields = False
314 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
311 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
315 return _UserPermsForm
312 return _UserPermsForm
316
313
317
314
318 def RepoFieldForm(localizer):
315 def RepoFieldForm(localizer):
319 _ = localizer
316 _ = localizer
320
317
321 class _RepoFieldForm(formencode.Schema):
318 class _RepoFieldForm(formencode.Schema):
322 filter_extra_fields = True
319 filter_extra_fields = True
323 allow_extra_fields = True
320 allow_extra_fields = True
324
321
325 new_field_key = All(v.FieldKey(localizer),
322 new_field_key = All(v.FieldKey(localizer),
326 v.UnicodeString(strip=True, min=3, not_empty=True))
323 v.UnicodeString(strip=True, min=3, not_empty=True))
327 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
324 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
328 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
325 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
329 if_missing='str')
326 if_missing='str')
330 new_field_label = v.UnicodeString(not_empty=False)
327 new_field_label = v.UnicodeString(not_empty=False)
331 new_field_desc = v.UnicodeString(not_empty=False)
328 new_field_desc = v.UnicodeString(not_empty=False)
332 return _RepoFieldForm
329 return _RepoFieldForm
333
330
334
331
335 def RepoForkForm(localizer, edit=False, old_data=None,
332 def RepoForkForm(localizer, edit=False, old_data=None,
336 supported_backends=BACKENDS.keys(), repo_groups=None,
333 supported_backends=BACKENDS.keys(), repo_groups=None):
337 landing_revs=None):
338 _ = localizer
334 _ = localizer
339 old_data = old_data or {}
335 old_data = old_data or {}
340 repo_groups = repo_groups or []
336 repo_groups = repo_groups or []
341 landing_revs = landing_revs or []
342
337
343 class _RepoForkForm(formencode.Schema):
338 class _RepoForkForm(formencode.Schema):
344 allow_extra_fields = True
339 allow_extra_fields = True
345 filter_extra_fields = False
340 filter_extra_fields = False
346 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
341 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
347 v.SlugifyName(localizer))
342 v.SlugifyName(localizer))
348 repo_group = All(v.CanWriteGroup(localizer, ),
343 repo_group = All(v.CanWriteGroup(localizer, ),
349 v.OneOf(repo_groups, hideList=True))
344 v.OneOf(repo_groups, hideList=True))
350 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
345 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
351 description = v.UnicodeString(strip=True, min=1, not_empty=True)
346 description = v.UnicodeString(strip=True, min=1, not_empty=True)
352 private = v.StringBoolean(if_missing=False)
347 private = v.StringBoolean(if_missing=False)
353 copy_permissions = v.StringBoolean(if_missing=False)
348 copy_permissions = v.StringBoolean(if_missing=False)
354 fork_parent_id = v.UnicodeString()
349 fork_parent_id = v.UnicodeString()
355 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
350 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
356 landing_rev = v.OneOf(landing_revs, hideList=True)
357 return _RepoForkForm
351 return _RepoForkForm
358
352
359
353
360 def ApplicationSettingsForm(localizer):
354 def ApplicationSettingsForm(localizer):
361 _ = localizer
355 _ = localizer
362
356
363 class _ApplicationSettingsForm(formencode.Schema):
357 class _ApplicationSettingsForm(formencode.Schema):
364 allow_extra_fields = True
358 allow_extra_fields = True
365 filter_extra_fields = False
359 filter_extra_fields = False
366 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
360 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
367 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
361 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
368 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
362 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
369 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
363 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
370 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
364 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
371 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
365 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
372 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
366 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
373 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
367 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
374 return _ApplicationSettingsForm
368 return _ApplicationSettingsForm
375
369
376
370
377 def ApplicationVisualisationForm(localizer):
371 def ApplicationVisualisationForm(localizer):
378 from rhodecode.model.db import Repository
372 from rhodecode.model.db import Repository
379 _ = localizer
373 _ = localizer
380
374
381 class _ApplicationVisualisationForm(formencode.Schema):
375 class _ApplicationVisualisationForm(formencode.Schema):
382 allow_extra_fields = True
376 allow_extra_fields = True
383 filter_extra_fields = False
377 filter_extra_fields = False
384 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
378 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
385 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
379 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
386 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
380 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
387
381
388 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
382 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
389 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
383 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
390 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
384 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
391 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
385 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
392 rhodecode_show_version = v.StringBoolean(if_missing=False)
386 rhodecode_show_version = v.StringBoolean(if_missing=False)
393 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
387 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
394 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
388 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
395 rhodecode_gravatar_url = v.UnicodeString(min=3)
389 rhodecode_gravatar_url = v.UnicodeString(min=3)
396 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
390 rhodecode_clone_uri_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI)
397 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
391 rhodecode_clone_uri_ssh_tmpl = v.UnicodeString(not_empty=False, if_empty=Repository.DEFAULT_CLONE_URI_SSH)
398 rhodecode_support_url = v.UnicodeString()
392 rhodecode_support_url = v.UnicodeString()
399 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
393 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
400 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
394 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
401 return _ApplicationVisualisationForm
395 return _ApplicationVisualisationForm
402
396
403
397
404 class _BaseVcsSettingsForm(formencode.Schema):
398 class _BaseVcsSettingsForm(formencode.Schema):
405
399
406 allow_extra_fields = True
400 allow_extra_fields = True
407 filter_extra_fields = False
401 filter_extra_fields = False
408 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
402 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
409 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
403 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
410 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
404 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
411
405
412 # PR/Code-review
406 # PR/Code-review
413 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
407 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
414 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
408 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
415
409
416 # hg
410 # hg
417 extensions_largefiles = v.StringBoolean(if_missing=False)
411 extensions_largefiles = v.StringBoolean(if_missing=False)
418 extensions_evolve = v.StringBoolean(if_missing=False)
412 extensions_evolve = v.StringBoolean(if_missing=False)
419 phases_publish = v.StringBoolean(if_missing=False)
413 phases_publish = v.StringBoolean(if_missing=False)
420
414
421 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
415 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
422 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
416 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
423
417
424 # git
418 # git
425 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
419 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
426 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
420 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
427 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
421 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
428
422
429 # svn
423 # svn
430 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
424 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
431 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
425 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
432
426
433 # cache
427 # cache
434 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
428 rhodecode_diff_cache = v.StringBoolean(if_missing=False)
435
429
436
430
437 def ApplicationUiSettingsForm(localizer):
431 def ApplicationUiSettingsForm(localizer):
438 _ = localizer
432 _ = localizer
439
433
440 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
434 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
441 web_push_ssl = v.StringBoolean(if_missing=False)
435 web_push_ssl = v.StringBoolean(if_missing=False)
442 paths_root_path = All(
436 paths_root_path = All(
443 v.ValidPath(localizer),
437 v.ValidPath(localizer),
444 v.UnicodeString(strip=True, min=1, not_empty=True)
438 v.UnicodeString(strip=True, min=1, not_empty=True)
445 )
439 )
446 largefiles_usercache = All(
440 largefiles_usercache = All(
447 v.ValidPath(localizer),
441 v.ValidPath(localizer),
448 v.UnicodeString(strip=True, min=2, not_empty=True))
442 v.UnicodeString(strip=True, min=2, not_empty=True))
449 vcs_git_lfs_store_location = All(
443 vcs_git_lfs_store_location = All(
450 v.ValidPath(localizer),
444 v.ValidPath(localizer),
451 v.UnicodeString(strip=True, min=2, not_empty=True))
445 v.UnicodeString(strip=True, min=2, not_empty=True))
452 extensions_hgsubversion = v.StringBoolean(if_missing=False)
446 extensions_hgsubversion = v.StringBoolean(if_missing=False)
453 extensions_hggit = v.StringBoolean(if_missing=False)
447 extensions_hggit = v.StringBoolean(if_missing=False)
454 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
448 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
455 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
449 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
456 return _ApplicationUiSettingsForm
450 return _ApplicationUiSettingsForm
457
451
458
452
459 def RepoVcsSettingsForm(localizer, repo_name):
453 def RepoVcsSettingsForm(localizer, repo_name):
460 _ = localizer
454 _ = localizer
461
455
462 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
456 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
463 inherit_global_settings = v.StringBoolean(if_missing=False)
457 inherit_global_settings = v.StringBoolean(if_missing=False)
464 new_svn_branch = v.ValidSvnPattern(localizer,
458 new_svn_branch = v.ValidSvnPattern(localizer,
465 section='vcs_svn_branch', repo_name=repo_name)
459 section='vcs_svn_branch', repo_name=repo_name)
466 new_svn_tag = v.ValidSvnPattern(localizer,
460 new_svn_tag = v.ValidSvnPattern(localizer,
467 section='vcs_svn_tag', repo_name=repo_name)
461 section='vcs_svn_tag', repo_name=repo_name)
468 return _RepoVcsSettingsForm
462 return _RepoVcsSettingsForm
469
463
470
464
471 def LabsSettingsForm(localizer):
465 def LabsSettingsForm(localizer):
472 _ = localizer
466 _ = localizer
473
467
474 class _LabSettingsForm(formencode.Schema):
468 class _LabSettingsForm(formencode.Schema):
475 allow_extra_fields = True
469 allow_extra_fields = True
476 filter_extra_fields = False
470 filter_extra_fields = False
477 return _LabSettingsForm
471 return _LabSettingsForm
478
472
479
473
480 def ApplicationPermissionsForm(
474 def ApplicationPermissionsForm(
481 localizer, register_choices, password_reset_choices,
475 localizer, register_choices, password_reset_choices,
482 extern_activate_choices):
476 extern_activate_choices):
483 _ = localizer
477 _ = localizer
484
478
485 class _DefaultPermissionsForm(formencode.Schema):
479 class _DefaultPermissionsForm(formencode.Schema):
486 allow_extra_fields = True
480 allow_extra_fields = True
487 filter_extra_fields = True
481 filter_extra_fields = True
488
482
489 anonymous = v.StringBoolean(if_missing=False)
483 anonymous = v.StringBoolean(if_missing=False)
490 default_register = v.OneOf(register_choices)
484 default_register = v.OneOf(register_choices)
491 default_register_message = v.UnicodeString()
485 default_register_message = v.UnicodeString()
492 default_password_reset = v.OneOf(password_reset_choices)
486 default_password_reset = v.OneOf(password_reset_choices)
493 default_extern_activate = v.OneOf(extern_activate_choices)
487 default_extern_activate = v.OneOf(extern_activate_choices)
494 return _DefaultPermissionsForm
488 return _DefaultPermissionsForm
495
489
496
490
497 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
491 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
498 user_group_perms_choices):
492 user_group_perms_choices):
499 _ = localizer
493 _ = localizer
500
494
501 class _ObjectPermissionsForm(formencode.Schema):
495 class _ObjectPermissionsForm(formencode.Schema):
502 allow_extra_fields = True
496 allow_extra_fields = True
503 filter_extra_fields = True
497 filter_extra_fields = True
504 overwrite_default_repo = v.StringBoolean(if_missing=False)
498 overwrite_default_repo = v.StringBoolean(if_missing=False)
505 overwrite_default_group = v.StringBoolean(if_missing=False)
499 overwrite_default_group = v.StringBoolean(if_missing=False)
506 overwrite_default_user_group = v.StringBoolean(if_missing=False)
500 overwrite_default_user_group = v.StringBoolean(if_missing=False)
507
501
508 default_repo_perm = v.OneOf(repo_perms_choices)
502 default_repo_perm = v.OneOf(repo_perms_choices)
509 default_group_perm = v.OneOf(group_perms_choices)
503 default_group_perm = v.OneOf(group_perms_choices)
510 default_user_group_perm = v.OneOf(user_group_perms_choices)
504 default_user_group_perm = v.OneOf(user_group_perms_choices)
511
505
512 return _ObjectPermissionsForm
506 return _ObjectPermissionsForm
513
507
514
508
515 def BranchPermissionsForm(localizer, branch_perms_choices):
509 def BranchPermissionsForm(localizer, branch_perms_choices):
516 _ = localizer
510 _ = localizer
517
511
518 class _BranchPermissionsForm(formencode.Schema):
512 class _BranchPermissionsForm(formencode.Schema):
519 allow_extra_fields = True
513 allow_extra_fields = True
520 filter_extra_fields = True
514 filter_extra_fields = True
521 overwrite_default_branch = v.StringBoolean(if_missing=False)
515 overwrite_default_branch = v.StringBoolean(if_missing=False)
522 default_branch_perm = v.OneOf(branch_perms_choices)
516 default_branch_perm = v.OneOf(branch_perms_choices)
523
517
524 return _BranchPermissionsForm
518 return _BranchPermissionsForm
525
519
526
520
527 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
521 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
528 repo_group_create_choices, user_group_create_choices,
522 repo_group_create_choices, user_group_create_choices,
529 fork_choices, inherit_default_permissions_choices):
523 fork_choices, inherit_default_permissions_choices):
530 _ = localizer
524 _ = localizer
531
525
532 class _DefaultPermissionsForm(formencode.Schema):
526 class _DefaultPermissionsForm(formencode.Schema):
533 allow_extra_fields = True
527 allow_extra_fields = True
534 filter_extra_fields = True
528 filter_extra_fields = True
535
529
536 anonymous = v.StringBoolean(if_missing=False)
530 anonymous = v.StringBoolean(if_missing=False)
537
531
538 default_repo_create = v.OneOf(create_choices)
532 default_repo_create = v.OneOf(create_choices)
539 default_repo_create_on_write = v.OneOf(create_on_write_choices)
533 default_repo_create_on_write = v.OneOf(create_on_write_choices)
540 default_user_group_create = v.OneOf(user_group_create_choices)
534 default_user_group_create = v.OneOf(user_group_create_choices)
541 default_repo_group_create = v.OneOf(repo_group_create_choices)
535 default_repo_group_create = v.OneOf(repo_group_create_choices)
542 default_fork_create = v.OneOf(fork_choices)
536 default_fork_create = v.OneOf(fork_choices)
543 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
537 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
544 return _DefaultPermissionsForm
538 return _DefaultPermissionsForm
545
539
546
540
547 def UserIndividualPermissionsForm(localizer):
541 def UserIndividualPermissionsForm(localizer):
548 _ = localizer
542 _ = localizer
549
543
550 class _DefaultPermissionsForm(formencode.Schema):
544 class _DefaultPermissionsForm(formencode.Schema):
551 allow_extra_fields = True
545 allow_extra_fields = True
552 filter_extra_fields = True
546 filter_extra_fields = True
553
547
554 inherit_default_permissions = v.StringBoolean(if_missing=False)
548 inherit_default_permissions = v.StringBoolean(if_missing=False)
555 return _DefaultPermissionsForm
549 return _DefaultPermissionsForm
556
550
557
551
558 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
552 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
559 _ = localizer
553 _ = localizer
560 old_data = old_data or {}
554 old_data = old_data or {}
561
555
562 class _DefaultsForm(formencode.Schema):
556 class _DefaultsForm(formencode.Schema):
563 allow_extra_fields = True
557 allow_extra_fields = True
564 filter_extra_fields = True
558 filter_extra_fields = True
565 default_repo_type = v.OneOf(supported_backends)
559 default_repo_type = v.OneOf(supported_backends)
566 default_repo_private = v.StringBoolean(if_missing=False)
560 default_repo_private = v.StringBoolean(if_missing=False)
567 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
561 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
568 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
562 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
569 default_repo_enable_locking = v.StringBoolean(if_missing=False)
563 default_repo_enable_locking = v.StringBoolean(if_missing=False)
570 return _DefaultsForm
564 return _DefaultsForm
571
565
572
566
573 def AuthSettingsForm(localizer):
567 def AuthSettingsForm(localizer):
574 _ = localizer
568 _ = localizer
575
569
576 class _AuthSettingsForm(formencode.Schema):
570 class _AuthSettingsForm(formencode.Schema):
577 allow_extra_fields = True
571 allow_extra_fields = True
578 filter_extra_fields = True
572 filter_extra_fields = True
579 auth_plugins = All(v.ValidAuthPlugins(localizer),
573 auth_plugins = All(v.ValidAuthPlugins(localizer),
580 v.UniqueListFromString(localizer)(not_empty=True))
574 v.UniqueListFromString(localizer)(not_empty=True))
581 return _AuthSettingsForm
575 return _AuthSettingsForm
582
576
583
577
584 def UserExtraEmailForm(localizer):
578 def UserExtraEmailForm(localizer):
585 _ = localizer
579 _ = localizer
586
580
587 class _UserExtraEmailForm(formencode.Schema):
581 class _UserExtraEmailForm(formencode.Schema):
588 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
582 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
589 return _UserExtraEmailForm
583 return _UserExtraEmailForm
590
584
591
585
592 def UserExtraIpForm(localizer):
586 def UserExtraIpForm(localizer):
593 _ = localizer
587 _ = localizer
594
588
595 class _UserExtraIpForm(formencode.Schema):
589 class _UserExtraIpForm(formencode.Schema):
596 ip = v.ValidIp(localizer)(not_empty=True)
590 ip = v.ValidIp(localizer)(not_empty=True)
597 return _UserExtraIpForm
591 return _UserExtraIpForm
598
592
599
593
600 def PullRequestForm(localizer, repo_id):
594 def PullRequestForm(localizer, repo_id):
601 _ = localizer
595 _ = localizer
602
596
603 class ReviewerForm(formencode.Schema):
597 class ReviewerForm(formencode.Schema):
604 user_id = v.Int(not_empty=True)
598 user_id = v.Int(not_empty=True)
605 reasons = All()
599 reasons = All()
606 rules = All(v.UniqueList(localizer, convert=int)())
600 rules = All(v.UniqueList(localizer, convert=int)())
607 mandatory = v.StringBoolean()
601 mandatory = v.StringBoolean()
608
602
609 class _PullRequestForm(formencode.Schema):
603 class _PullRequestForm(formencode.Schema):
610 allow_extra_fields = True
604 allow_extra_fields = True
611 filter_extra_fields = True
605 filter_extra_fields = True
612
606
613 common_ancestor = v.UnicodeString(strip=True, required=True)
607 common_ancestor = v.UnicodeString(strip=True, required=True)
614 source_repo = v.UnicodeString(strip=True, required=True)
608 source_repo = v.UnicodeString(strip=True, required=True)
615 source_ref = v.UnicodeString(strip=True, required=True)
609 source_ref = v.UnicodeString(strip=True, required=True)
616 target_repo = v.UnicodeString(strip=True, required=True)
610 target_repo = v.UnicodeString(strip=True, required=True)
617 target_ref = v.UnicodeString(strip=True, required=True)
611 target_ref = v.UnicodeString(strip=True, required=True)
618 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
612 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
619 v.UniqueList(localizer)(not_empty=True))
613 v.UniqueList(localizer)(not_empty=True))
620 review_members = formencode.ForEach(ReviewerForm())
614 review_members = formencode.ForEach(ReviewerForm())
621 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
615 pullrequest_title = v.UnicodeString(strip=True, required=True, min=1, max=255)
622 pullrequest_desc = v.UnicodeString(strip=True, required=False)
616 pullrequest_desc = v.UnicodeString(strip=True, required=False)
623 description_renderer = v.UnicodeString(strip=True, required=False)
617 description_renderer = v.UnicodeString(strip=True, required=False)
624
618
625 return _PullRequestForm
619 return _PullRequestForm
626
620
627
621
628 def IssueTrackerPatternsForm(localizer):
622 def IssueTrackerPatternsForm(localizer):
629 _ = localizer
623 _ = localizer
630
624
631 class _IssueTrackerPatternsForm(formencode.Schema):
625 class _IssueTrackerPatternsForm(formencode.Schema):
632 allow_extra_fields = True
626 allow_extra_fields = True
633 filter_extra_fields = False
627 filter_extra_fields = False
634 chained_validators = [v.ValidPattern(localizer)]
628 chained_validators = [v.ValidPattern(localizer)]
635 return _IssueTrackerPatternsForm
629 return _IssueTrackerPatternsForm
@@ -1,942 +1,972 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-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 """
21 """
22 Scm model for RhodeCode
22 Scm model for RhodeCode
23 """
23 """
24
24
25 import os.path
25 import os.path
26 import traceback
26 import traceback
27 import logging
27 import logging
28 import cStringIO
28 import cStringIO
29
29
30 from sqlalchemy import func
30 from sqlalchemy import func
31 from zope.cachedescriptors.property import Lazy as LazyProperty
31 from zope.cachedescriptors.property import Lazy as LazyProperty
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib.vcs import get_backend
34 from rhodecode.lib.vcs import get_backend
35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
35 from rhodecode.lib.vcs.exceptions import RepositoryError, NodeNotChangedError
36 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.lib.vcs.nodes import FileNode
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.backends.base import EmptyCommit
38 from rhodecode.lib import helpers as h, rc_cache
38 from rhodecode.lib import helpers as h, rc_cache
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
40 HasRepoPermissionAny, HasRepoGroupPermissionAny,
41 HasUserGroupPermissionAny)
41 HasUserGroupPermissionAny)
42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
42 from rhodecode.lib.exceptions import NonRelativePathError, IMCCommitError
43 from rhodecode.lib import hooks_utils
43 from rhodecode.lib import hooks_utils
44 from rhodecode.lib.utils import (
44 from rhodecode.lib.utils import (
45 get_filesystem_repos, make_db_config)
45 get_filesystem_repos, make_db_config)
46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
46 from rhodecode.lib.utils2 import (safe_str, safe_unicode)
47 from rhodecode.lib.system_info import get_system_info
47 from rhodecode.lib.system_info import get_system_info
48 from rhodecode.model import BaseModel
48 from rhodecode.model import BaseModel
49 from rhodecode.model.db import (
49 from rhodecode.model.db import (
50 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
50 Repository, CacheKey, UserFollowing, UserLog, User, RepoGroup,
51 PullRequest)
51 PullRequest)
52 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
53 from rhodecode.model.validation_schema.validators import url_validator, InvalidCloneUrl
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UserTemp(object):
58 class UserTemp(object):
59 def __init__(self, user_id):
59 def __init__(self, user_id):
60 self.user_id = user_id
60 self.user_id = user_id
61
61
62 def __repr__(self):
62 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64
64
65
65
66 class RepoTemp(object):
66 class RepoTemp(object):
67 def __init__(self, repo_id):
67 def __init__(self, repo_id):
68 self.repo_id = repo_id
68 self.repo_id = repo_id
69
69
70 def __repr__(self):
70 def __repr__(self):
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72
72
73
73
74 class SimpleCachedRepoList(object):
74 class SimpleCachedRepoList(object):
75 """
75 """
76 Lighter version of of iteration of repos without the scm initialisation,
76 Lighter version of of iteration of repos without the scm initialisation,
77 and with cache usage
77 and with cache usage
78 """
78 """
79 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
79 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
80 self.db_repo_list = db_repo_list
80 self.db_repo_list = db_repo_list
81 self.repos_path = repos_path
81 self.repos_path = repos_path
82 self.order_by = order_by
82 self.order_by = order_by
83 self.reversed = (order_by or '').startswith('-')
83 self.reversed = (order_by or '').startswith('-')
84 if not perm_set:
84 if not perm_set:
85 perm_set = ['repository.read', 'repository.write',
85 perm_set = ['repository.read', 'repository.write',
86 'repository.admin']
86 'repository.admin']
87 self.perm_set = perm_set
87 self.perm_set = perm_set
88
88
89 def __len__(self):
89 def __len__(self):
90 return len(self.db_repo_list)
90 return len(self.db_repo_list)
91
91
92 def __repr__(self):
92 def __repr__(self):
93 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
93 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
94
94
95 def __iter__(self):
95 def __iter__(self):
96 for dbr in self.db_repo_list:
96 for dbr in self.db_repo_list:
97 # check permission at this level
97 # check permission at this level
98 has_perm = HasRepoPermissionAny(*self.perm_set)(
98 has_perm = HasRepoPermissionAny(*self.perm_set)(
99 dbr.repo_name, 'SimpleCachedRepoList check')
99 dbr.repo_name, 'SimpleCachedRepoList check')
100 if not has_perm:
100 if not has_perm:
101 continue
101 continue
102
102
103 tmp_d = {
103 tmp_d = {
104 'name': dbr.repo_name,
104 'name': dbr.repo_name,
105 'dbrepo': dbr.get_dict(),
105 'dbrepo': dbr.get_dict(),
106 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
106 'dbrepo_fork': dbr.fork.get_dict() if dbr.fork else {}
107 }
107 }
108 yield tmp_d
108 yield tmp_d
109
109
110
110
111 class _PermCheckIterator(object):
111 class _PermCheckIterator(object):
112
112
113 def __init__(
113 def __init__(
114 self, obj_list, obj_attr, perm_set, perm_checker,
114 self, obj_list, obj_attr, perm_set, perm_checker,
115 extra_kwargs=None):
115 extra_kwargs=None):
116 """
116 """
117 Creates iterator from given list of objects, additionally
117 Creates iterator from given list of objects, additionally
118 checking permission for them from perm_set var
118 checking permission for them from perm_set var
119
119
120 :param obj_list: list of db objects
120 :param obj_list: list of db objects
121 :param obj_attr: attribute of object to pass into perm_checker
121 :param obj_attr: attribute of object to pass into perm_checker
122 :param perm_set: list of permissions to check
122 :param perm_set: list of permissions to check
123 :param perm_checker: callable to check permissions against
123 :param perm_checker: callable to check permissions against
124 """
124 """
125 self.obj_list = obj_list
125 self.obj_list = obj_list
126 self.obj_attr = obj_attr
126 self.obj_attr = obj_attr
127 self.perm_set = perm_set
127 self.perm_set = perm_set
128 self.perm_checker = perm_checker
128 self.perm_checker = perm_checker
129 self.extra_kwargs = extra_kwargs or {}
129 self.extra_kwargs = extra_kwargs or {}
130
130
131 def __len__(self):
131 def __len__(self):
132 return len(self.obj_list)
132 return len(self.obj_list)
133
133
134 def __repr__(self):
134 def __repr__(self):
135 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
135 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
136
136
137 def __iter__(self):
137 def __iter__(self):
138 checker = self.perm_checker(*self.perm_set)
138 checker = self.perm_checker(*self.perm_set)
139 for db_obj in self.obj_list:
139 for db_obj in self.obj_list:
140 # check permission at this level
140 # check permission at this level
141 name = getattr(db_obj, self.obj_attr, None)
141 name = getattr(db_obj, self.obj_attr, None)
142 if not checker(name, self.__class__.__name__, **self.extra_kwargs):
142 if not checker(name, self.__class__.__name__, **self.extra_kwargs):
143 continue
143 continue
144
144
145 yield db_obj
145 yield db_obj
146
146
147
147
148 class RepoList(_PermCheckIterator):
148 class RepoList(_PermCheckIterator):
149
149
150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
150 def __init__(self, db_repo_list, perm_set=None, extra_kwargs=None):
151 if not perm_set:
151 if not perm_set:
152 perm_set = [
152 perm_set = [
153 'repository.read', 'repository.write', 'repository.admin']
153 'repository.read', 'repository.write', 'repository.admin']
154
154
155 super(RepoList, self).__init__(
155 super(RepoList, self).__init__(
156 obj_list=db_repo_list,
156 obj_list=db_repo_list,
157 obj_attr='repo_name', perm_set=perm_set,
157 obj_attr='repo_name', perm_set=perm_set,
158 perm_checker=HasRepoPermissionAny,
158 perm_checker=HasRepoPermissionAny,
159 extra_kwargs=extra_kwargs)
159 extra_kwargs=extra_kwargs)
160
160
161
161
162 class RepoGroupList(_PermCheckIterator):
162 class RepoGroupList(_PermCheckIterator):
163
163
164 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
164 def __init__(self, db_repo_group_list, perm_set=None, extra_kwargs=None):
165 if not perm_set:
165 if not perm_set:
166 perm_set = ['group.read', 'group.write', 'group.admin']
166 perm_set = ['group.read', 'group.write', 'group.admin']
167
167
168 super(RepoGroupList, self).__init__(
168 super(RepoGroupList, self).__init__(
169 obj_list=db_repo_group_list,
169 obj_list=db_repo_group_list,
170 obj_attr='group_name', perm_set=perm_set,
170 obj_attr='group_name', perm_set=perm_set,
171 perm_checker=HasRepoGroupPermissionAny,
171 perm_checker=HasRepoGroupPermissionAny,
172 extra_kwargs=extra_kwargs)
172 extra_kwargs=extra_kwargs)
173
173
174
174
175 class UserGroupList(_PermCheckIterator):
175 class UserGroupList(_PermCheckIterator):
176
176
177 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
177 def __init__(self, db_user_group_list, perm_set=None, extra_kwargs=None):
178 if not perm_set:
178 if not perm_set:
179 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
179 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
180
180
181 super(UserGroupList, self).__init__(
181 super(UserGroupList, self).__init__(
182 obj_list=db_user_group_list,
182 obj_list=db_user_group_list,
183 obj_attr='users_group_name', perm_set=perm_set,
183 obj_attr='users_group_name', perm_set=perm_set,
184 perm_checker=HasUserGroupPermissionAny,
184 perm_checker=HasUserGroupPermissionAny,
185 extra_kwargs=extra_kwargs)
185 extra_kwargs=extra_kwargs)
186
186
187
187
188 class ScmModel(BaseModel):
188 class ScmModel(BaseModel):
189 """
189 """
190 Generic Scm Model
190 Generic Scm Model
191 """
191 """
192
192
193 @LazyProperty
193 @LazyProperty
194 def repos_path(self):
194 def repos_path(self):
195 """
195 """
196 Gets the repositories root path from database
196 Gets the repositories root path from database
197 """
197 """
198
198
199 settings_model = VcsSettingsModel(sa=self.sa)
199 settings_model = VcsSettingsModel(sa=self.sa)
200 return settings_model.get_repos_location()
200 return settings_model.get_repos_location()
201
201
202 def repo_scan(self, repos_path=None):
202 def repo_scan(self, repos_path=None):
203 """
203 """
204 Listing of repositories in given path. This path should not be a
204 Listing of repositories in given path. This path should not be a
205 repository itself. Return a dictionary of repository objects
205 repository itself. Return a dictionary of repository objects
206
206
207 :param repos_path: path to directory containing repositories
207 :param repos_path: path to directory containing repositories
208 """
208 """
209
209
210 if repos_path is None:
210 if repos_path is None:
211 repos_path = self.repos_path
211 repos_path = self.repos_path
212
212
213 log.info('scanning for repositories in %s', repos_path)
213 log.info('scanning for repositories in %s', repos_path)
214
214
215 config = make_db_config()
215 config = make_db_config()
216 config.set('extensions', 'largefiles', '')
216 config.set('extensions', 'largefiles', '')
217 repos = {}
217 repos = {}
218
218
219 for name, path in get_filesystem_repos(repos_path, recursive=True):
219 for name, path in get_filesystem_repos(repos_path, recursive=True):
220 # name need to be decomposed and put back together using the /
220 # name need to be decomposed and put back together using the /
221 # since this is internal storage separator for rhodecode
221 # since this is internal storage separator for rhodecode
222 name = Repository.normalize_repo_name(name)
222 name = Repository.normalize_repo_name(name)
223
223
224 try:
224 try:
225 if name in repos:
225 if name in repos:
226 raise RepositoryError('Duplicate repository name %s '
226 raise RepositoryError('Duplicate repository name %s '
227 'found in %s' % (name, path))
227 'found in %s' % (name, path))
228 elif path[0] in rhodecode.BACKENDS:
228 elif path[0] in rhodecode.BACKENDS:
229 backend = get_backend(path[0])
229 backend = get_backend(path[0])
230 repos[name] = backend(path[1], config=config,
230 repos[name] = backend(path[1], config=config,
231 with_wire={"cache": False})
231 with_wire={"cache": False})
232 except OSError:
232 except OSError:
233 continue
233 continue
234 log.debug('found %s paths with repositories', len(repos))
234 log.debug('found %s paths with repositories', len(repos))
235 return repos
235 return repos
236
236
237 def get_repos(self, all_repos=None, sort_key=None):
237 def get_repos(self, all_repos=None, sort_key=None):
238 """
238 """
239 Get all repositories from db and for each repo create it's
239 Get all repositories from db and for each repo create it's
240 backend instance and fill that backed with information from database
240 backend instance and fill that backed with information from database
241
241
242 :param all_repos: list of repository names as strings
242 :param all_repos: list of repository names as strings
243 give specific repositories list, good for filtering
243 give specific repositories list, good for filtering
244
244
245 :param sort_key: initial sorting of repositories
245 :param sort_key: initial sorting of repositories
246 """
246 """
247 if all_repos is None:
247 if all_repos is None:
248 all_repos = self.sa.query(Repository)\
248 all_repos = self.sa.query(Repository)\
249 .filter(Repository.group_id == None)\
249 .filter(Repository.group_id == None)\
250 .order_by(func.lower(Repository.repo_name)).all()
250 .order_by(func.lower(Repository.repo_name)).all()
251 repo_iter = SimpleCachedRepoList(
251 repo_iter = SimpleCachedRepoList(
252 all_repos, repos_path=self.repos_path, order_by=sort_key)
252 all_repos, repos_path=self.repos_path, order_by=sort_key)
253 return repo_iter
253 return repo_iter
254
254
255 def get_repo_groups(self, all_groups=None):
255 def get_repo_groups(self, all_groups=None):
256 if all_groups is None:
256 if all_groups is None:
257 all_groups = RepoGroup.query()\
257 all_groups = RepoGroup.query()\
258 .filter(RepoGroup.group_parent_id == None).all()
258 .filter(RepoGroup.group_parent_id == None).all()
259 return [x for x in RepoGroupList(all_groups)]
259 return [x for x in RepoGroupList(all_groups)]
260
260
261 def mark_for_invalidation(self, repo_name, delete=False):
261 def mark_for_invalidation(self, repo_name, delete=False):
262 """
262 """
263 Mark caches of this repo invalid in the database. `delete` flag
263 Mark caches of this repo invalid in the database. `delete` flag
264 removes the cache entries
264 removes the cache entries
265
265
266 :param repo_name: the repo_name for which caches should be marked
266 :param repo_name: the repo_name for which caches should be marked
267 invalid, or deleted
267 invalid, or deleted
268 :param delete: delete the entry keys instead of setting bool
268 :param delete: delete the entry keys instead of setting bool
269 flag on them, and also purge caches used by the dogpile
269 flag on them, and also purge caches used by the dogpile
270 """
270 """
271 repo = Repository.get_by_repo_name(repo_name)
271 repo = Repository.get_by_repo_name(repo_name)
272
272
273 if repo:
273 if repo:
274 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
274 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
275 repo_id=repo.repo_id)
275 repo_id=repo.repo_id)
276 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
276 CacheKey.set_invalidate(invalidation_namespace, delete=delete)
277
277
278 repo_id = repo.repo_id
278 repo_id = repo.repo_id
279 config = repo._config
279 config = repo._config
280 config.set('extensions', 'largefiles', '')
280 config.set('extensions', 'largefiles', '')
281 repo.update_commit_cache(config=config, cs_cache=None)
281 repo.update_commit_cache(config=config, cs_cache=None)
282 if delete:
282 if delete:
283 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
283 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
284 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
284 rc_cache.clear_cache_namespace('cache_repo', cache_namespace_uid)
285
285
286 def toggle_following_repo(self, follow_repo_id, user_id):
286 def toggle_following_repo(self, follow_repo_id, user_id):
287
287
288 f = self.sa.query(UserFollowing)\
288 f = self.sa.query(UserFollowing)\
289 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
289 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
290 .filter(UserFollowing.user_id == user_id).scalar()
290 .filter(UserFollowing.user_id == user_id).scalar()
291
291
292 if f is not None:
292 if f is not None:
293 try:
293 try:
294 self.sa.delete(f)
294 self.sa.delete(f)
295 return
295 return
296 except Exception:
296 except Exception:
297 log.error(traceback.format_exc())
297 log.error(traceback.format_exc())
298 raise
298 raise
299
299
300 try:
300 try:
301 f = UserFollowing()
301 f = UserFollowing()
302 f.user_id = user_id
302 f.user_id = user_id
303 f.follows_repo_id = follow_repo_id
303 f.follows_repo_id = follow_repo_id
304 self.sa.add(f)
304 self.sa.add(f)
305 except Exception:
305 except Exception:
306 log.error(traceback.format_exc())
306 log.error(traceback.format_exc())
307 raise
307 raise
308
308
309 def toggle_following_user(self, follow_user_id, user_id):
309 def toggle_following_user(self, follow_user_id, user_id):
310 f = self.sa.query(UserFollowing)\
310 f = self.sa.query(UserFollowing)\
311 .filter(UserFollowing.follows_user_id == follow_user_id)\
311 .filter(UserFollowing.follows_user_id == follow_user_id)\
312 .filter(UserFollowing.user_id == user_id).scalar()
312 .filter(UserFollowing.user_id == user_id).scalar()
313
313
314 if f is not None:
314 if f is not None:
315 try:
315 try:
316 self.sa.delete(f)
316 self.sa.delete(f)
317 return
317 return
318 except Exception:
318 except Exception:
319 log.error(traceback.format_exc())
319 log.error(traceback.format_exc())
320 raise
320 raise
321
321
322 try:
322 try:
323 f = UserFollowing()
323 f = UserFollowing()
324 f.user_id = user_id
324 f.user_id = user_id
325 f.follows_user_id = follow_user_id
325 f.follows_user_id = follow_user_id
326 self.sa.add(f)
326 self.sa.add(f)
327 except Exception:
327 except Exception:
328 log.error(traceback.format_exc())
328 log.error(traceback.format_exc())
329 raise
329 raise
330
330
331 def is_following_repo(self, repo_name, user_id, cache=False):
331 def is_following_repo(self, repo_name, user_id, cache=False):
332 r = self.sa.query(Repository)\
332 r = self.sa.query(Repository)\
333 .filter(Repository.repo_name == repo_name).scalar()
333 .filter(Repository.repo_name == repo_name).scalar()
334
334
335 f = self.sa.query(UserFollowing)\
335 f = self.sa.query(UserFollowing)\
336 .filter(UserFollowing.follows_repository == r)\
336 .filter(UserFollowing.follows_repository == r)\
337 .filter(UserFollowing.user_id == user_id).scalar()
337 .filter(UserFollowing.user_id == user_id).scalar()
338
338
339 return f is not None
339 return f is not None
340
340
341 def is_following_user(self, username, user_id, cache=False):
341 def is_following_user(self, username, user_id, cache=False):
342 u = User.get_by_username(username)
342 u = User.get_by_username(username)
343
343
344 f = self.sa.query(UserFollowing)\
344 f = self.sa.query(UserFollowing)\
345 .filter(UserFollowing.follows_user == u)\
345 .filter(UserFollowing.follows_user == u)\
346 .filter(UserFollowing.user_id == user_id).scalar()
346 .filter(UserFollowing.user_id == user_id).scalar()
347
347
348 return f is not None
348 return f is not None
349
349
350 def get_followers(self, repo):
350 def get_followers(self, repo):
351 repo = self._get_repo(repo)
351 repo = self._get_repo(repo)
352
352
353 return self.sa.query(UserFollowing)\
353 return self.sa.query(UserFollowing)\
354 .filter(UserFollowing.follows_repository == repo).count()
354 .filter(UserFollowing.follows_repository == repo).count()
355
355
356 def get_forks(self, repo):
356 def get_forks(self, repo):
357 repo = self._get_repo(repo)
357 repo = self._get_repo(repo)
358 return self.sa.query(Repository)\
358 return self.sa.query(Repository)\
359 .filter(Repository.fork == repo).count()
359 .filter(Repository.fork == repo).count()
360
360
361 def get_pull_requests(self, repo):
361 def get_pull_requests(self, repo):
362 repo = self._get_repo(repo)
362 repo = self._get_repo(repo)
363 return self.sa.query(PullRequest)\
363 return self.sa.query(PullRequest)\
364 .filter(PullRequest.target_repo == repo)\
364 .filter(PullRequest.target_repo == repo)\
365 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
365 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
366
366
367 def mark_as_fork(self, repo, fork, user):
367 def mark_as_fork(self, repo, fork, user):
368 repo = self._get_repo(repo)
368 repo = self._get_repo(repo)
369 fork = self._get_repo(fork)
369 fork = self._get_repo(fork)
370 if fork and repo.repo_id == fork.repo_id:
370 if fork and repo.repo_id == fork.repo_id:
371 raise Exception("Cannot set repository as fork of itself")
371 raise Exception("Cannot set repository as fork of itself")
372
372
373 if fork and repo.repo_type != fork.repo_type:
373 if fork and repo.repo_type != fork.repo_type:
374 raise RepositoryError(
374 raise RepositoryError(
375 "Cannot set repository as fork of repository with other type")
375 "Cannot set repository as fork of repository with other type")
376
376
377 repo.fork = fork
377 repo.fork = fork
378 self.sa.add(repo)
378 self.sa.add(repo)
379 return repo
379 return repo
380
380
381 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
381 def pull_changes(self, repo, username, remote_uri=None, validate_uri=True):
382 dbrepo = self._get_repo(repo)
382 dbrepo = self._get_repo(repo)
383 remote_uri = remote_uri or dbrepo.clone_uri
383 remote_uri = remote_uri or dbrepo.clone_uri
384 if not remote_uri:
384 if not remote_uri:
385 raise Exception("This repository doesn't have a clone uri")
385 raise Exception("This repository doesn't have a clone uri")
386
386
387 repo = dbrepo.scm_instance(cache=False)
387 repo = dbrepo.scm_instance(cache=False)
388 repo.config.clear_section('hooks')
388 repo.config.clear_section('hooks')
389
389
390 try:
390 try:
391 # NOTE(marcink): add extra validation so we skip invalid urls
391 # NOTE(marcink): add extra validation so we skip invalid urls
392 # this is due this tasks can be executed via scheduler without
392 # this is due this tasks can be executed via scheduler without
393 # proper validation of remote_uri
393 # proper validation of remote_uri
394 if validate_uri:
394 if validate_uri:
395 config = make_db_config(clear_session=False)
395 config = make_db_config(clear_session=False)
396 url_validator(remote_uri, dbrepo.repo_type, config)
396 url_validator(remote_uri, dbrepo.repo_type, config)
397 except InvalidCloneUrl:
397 except InvalidCloneUrl:
398 raise
398 raise
399
399
400 repo_name = dbrepo.repo_name
400 repo_name = dbrepo.repo_name
401 try:
401 try:
402 # TODO: we need to make sure those operations call proper hooks !
402 # TODO: we need to make sure those operations call proper hooks !
403 repo.fetch(remote_uri)
403 repo.fetch(remote_uri)
404
404
405 self.mark_for_invalidation(repo_name)
405 self.mark_for_invalidation(repo_name)
406 except Exception:
406 except Exception:
407 log.error(traceback.format_exc())
407 log.error(traceback.format_exc())
408 raise
408 raise
409
409
410 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
410 def push_changes(self, repo, username, remote_uri=None, validate_uri=True):
411 dbrepo = self._get_repo(repo)
411 dbrepo = self._get_repo(repo)
412 remote_uri = remote_uri or dbrepo.push_uri
412 remote_uri = remote_uri or dbrepo.push_uri
413 if not remote_uri:
413 if not remote_uri:
414 raise Exception("This repository doesn't have a clone uri")
414 raise Exception("This repository doesn't have a clone uri")
415
415
416 repo = dbrepo.scm_instance(cache=False)
416 repo = dbrepo.scm_instance(cache=False)
417 repo.config.clear_section('hooks')
417 repo.config.clear_section('hooks')
418
418
419 try:
419 try:
420 # NOTE(marcink): add extra validation so we skip invalid urls
420 # NOTE(marcink): add extra validation so we skip invalid urls
421 # this is due this tasks can be executed via scheduler without
421 # this is due this tasks can be executed via scheduler without
422 # proper validation of remote_uri
422 # proper validation of remote_uri
423 if validate_uri:
423 if validate_uri:
424 config = make_db_config(clear_session=False)
424 config = make_db_config(clear_session=False)
425 url_validator(remote_uri, dbrepo.repo_type, config)
425 url_validator(remote_uri, dbrepo.repo_type, config)
426 except InvalidCloneUrl:
426 except InvalidCloneUrl:
427 raise
427 raise
428
428
429 try:
429 try:
430 repo.push(remote_uri)
430 repo.push(remote_uri)
431 except Exception:
431 except Exception:
432 log.error(traceback.format_exc())
432 log.error(traceback.format_exc())
433 raise
433 raise
434
434
435 def commit_change(self, repo, repo_name, commit, user, author, message,
435 def commit_change(self, repo, repo_name, commit, user, author, message,
436 content, f_path):
436 content, f_path):
437 """
437 """
438 Commits changes
438 Commits changes
439
439
440 :param repo: SCM instance
440 :param repo: SCM instance
441
441
442 """
442 """
443 user = self._get_user(user)
443 user = self._get_user(user)
444
444
445 # decoding here will force that we have proper encoded values
445 # decoding here will force that we have proper encoded values
446 # in any other case this will throw exceptions and deny commit
446 # in any other case this will throw exceptions and deny commit
447 content = safe_str(content)
447 content = safe_str(content)
448 path = safe_str(f_path)
448 path = safe_str(f_path)
449 # message and author needs to be unicode
449 # message and author needs to be unicode
450 # proper backend should then translate that into required type
450 # proper backend should then translate that into required type
451 message = safe_unicode(message)
451 message = safe_unicode(message)
452 author = safe_unicode(author)
452 author = safe_unicode(author)
453 imc = repo.in_memory_commit
453 imc = repo.in_memory_commit
454 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
454 imc.change(FileNode(path, content, mode=commit.get_file_mode(f_path)))
455 try:
455 try:
456 # TODO: handle pre-push action !
456 # TODO: handle pre-push action !
457 tip = imc.commit(
457 tip = imc.commit(
458 message=message, author=author, parents=[commit],
458 message=message, author=author, parents=[commit],
459 branch=commit.branch)
459 branch=commit.branch)
460 except Exception as e:
460 except Exception as e:
461 log.error(traceback.format_exc())
461 log.error(traceback.format_exc())
462 raise IMCCommitError(str(e))
462 raise IMCCommitError(str(e))
463 finally:
463 finally:
464 # always clear caches, if commit fails we want fresh object also
464 # always clear caches, if commit fails we want fresh object also
465 self.mark_for_invalidation(repo_name)
465 self.mark_for_invalidation(repo_name)
466
466
467 # We trigger the post-push action
467 # We trigger the post-push action
468 hooks_utils.trigger_post_push_hook(
468 hooks_utils.trigger_post_push_hook(
469 username=user.username, action='push_local', hook_type='post_push',
469 username=user.username, action='push_local', hook_type='post_push',
470 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
470 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
471 return tip
471 return tip
472
472
473 def _sanitize_path(self, f_path):
473 def _sanitize_path(self, f_path):
474 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
474 if f_path.startswith('/') or f_path.startswith('./') or '../' in f_path:
475 raise NonRelativePathError('%s is not an relative path' % f_path)
475 raise NonRelativePathError('%s is not an relative path' % f_path)
476 if f_path:
476 if f_path:
477 f_path = os.path.normpath(f_path)
477 f_path = os.path.normpath(f_path)
478 return f_path
478 return f_path
479
479
480 def get_dirnode_metadata(self, request, commit, dir_node):
480 def get_dirnode_metadata(self, request, commit, dir_node):
481 if not dir_node.is_dir():
481 if not dir_node.is_dir():
482 return []
482 return []
483
483
484 data = []
484 data = []
485 for node in dir_node:
485 for node in dir_node:
486 if not node.is_file():
486 if not node.is_file():
487 # we skip file-nodes
487 # we skip file-nodes
488 continue
488 continue
489
489
490 last_commit = node.last_commit
490 last_commit = node.last_commit
491 last_commit_date = last_commit.date
491 last_commit_date = last_commit.date
492 data.append({
492 data.append({
493 'name': node.name,
493 'name': node.name,
494 'size': h.format_byte_size_binary(node.size),
494 'size': h.format_byte_size_binary(node.size),
495 'modified_at': h.format_date(last_commit_date),
495 'modified_at': h.format_date(last_commit_date),
496 'modified_ts': last_commit_date.isoformat(),
496 'modified_ts': last_commit_date.isoformat(),
497 'revision': last_commit.revision,
497 'revision': last_commit.revision,
498 'short_id': last_commit.short_id,
498 'short_id': last_commit.short_id,
499 'message': h.escape(last_commit.message),
499 'message': h.escape(last_commit.message),
500 'author': h.escape(last_commit.author),
500 'author': h.escape(last_commit.author),
501 'user_profile': h.gravatar_with_user(
501 'user_profile': h.gravatar_with_user(
502 request, last_commit.author),
502 request, last_commit.author),
503 })
503 })
504
504
505 return data
505 return data
506
506
507 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
507 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
508 extended_info=False, content=False, max_file_bytes=None):
508 extended_info=False, content=False, max_file_bytes=None):
509 """
509 """
510 recursive walk in root dir and return a set of all path in that dir
510 recursive walk in root dir and return a set of all path in that dir
511 based on repository walk function
511 based on repository walk function
512
512
513 :param repo_name: name of repository
513 :param repo_name: name of repository
514 :param commit_id: commit id for which to list nodes
514 :param commit_id: commit id for which to list nodes
515 :param root_path: root path to list
515 :param root_path: root path to list
516 :param flat: return as a list, if False returns a dict with description
516 :param flat: return as a list, if False returns a dict with description
517 :param extended_info: show additional info such as md5, binary, size etc
517 :param extended_info: show additional info such as md5, binary, size etc
518 :param content: add nodes content to the return data
518 :param content: add nodes content to the return data
519 :param max_file_bytes: will not return file contents over this limit
519 :param max_file_bytes: will not return file contents over this limit
520
520
521 """
521 """
522 _files = list()
522 _files = list()
523 _dirs = list()
523 _dirs = list()
524 try:
524 try:
525 _repo = self._get_repo(repo_name)
525 _repo = self._get_repo(repo_name)
526 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
526 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
527 root_path = root_path.lstrip('/')
527 root_path = root_path.lstrip('/')
528 for __, dirs, files in commit.walk(root_path):
528 for __, dirs, files in commit.walk(root_path):
529
529
530 for f in files:
530 for f in files:
531 _content = None
531 _content = None
532 _data = f_name = f.unicode_path
532 _data = f_name = f.unicode_path
533
533
534 if not flat:
534 if not flat:
535 _data = {
535 _data = {
536 "name": h.escape(f_name),
536 "name": h.escape(f_name),
537 "type": "file",
537 "type": "file",
538 }
538 }
539 if extended_info:
539 if extended_info:
540 _data.update({
540 _data.update({
541 "md5": f.md5,
541 "md5": f.md5,
542 "binary": f.is_binary,
542 "binary": f.is_binary,
543 "size": f.size,
543 "size": f.size,
544 "extension": f.extension,
544 "extension": f.extension,
545 "mimetype": f.mimetype,
545 "mimetype": f.mimetype,
546 "lines": f.lines()[0]
546 "lines": f.lines()[0]
547 })
547 })
548
548
549 if content:
549 if content:
550 over_size_limit = (max_file_bytes is not None
550 over_size_limit = (max_file_bytes is not None
551 and f.size > max_file_bytes)
551 and f.size > max_file_bytes)
552 full_content = None
552 full_content = None
553 if not f.is_binary and not over_size_limit:
553 if not f.is_binary and not over_size_limit:
554 full_content = safe_str(f.content)
554 full_content = safe_str(f.content)
555
555
556 _data.update({
556 _data.update({
557 "content": full_content,
557 "content": full_content,
558 })
558 })
559 _files.append(_data)
559 _files.append(_data)
560
560
561 for d in dirs:
561 for d in dirs:
562 _data = d_name = d.unicode_path
562 _data = d_name = d.unicode_path
563 if not flat:
563 if not flat:
564 _data = {
564 _data = {
565 "name": h.escape(d_name),
565 "name": h.escape(d_name),
566 "type": "dir",
566 "type": "dir",
567 }
567 }
568 if extended_info:
568 if extended_info:
569 _data.update({
569 _data.update({
570 "md5": None,
570 "md5": None,
571 "binary": None,
571 "binary": None,
572 "size": None,
572 "size": None,
573 "extension": None,
573 "extension": None,
574 })
574 })
575 if content:
575 if content:
576 _data.update({
576 _data.update({
577 "content": None
577 "content": None
578 })
578 })
579 _dirs.append(_data)
579 _dirs.append(_data)
580 except RepositoryError:
580 except RepositoryError:
581 log.exception("Exception in get_nodes")
581 log.exception("Exception in get_nodes")
582 raise
582 raise
583
583
584 return _dirs, _files
584 return _dirs, _files
585
585
586 def get_node(self, repo_name, commit_id, file_path,
586 def get_node(self, repo_name, commit_id, file_path,
587 extended_info=False, content=False, max_file_bytes=None, cache=True):
587 extended_info=False, content=False, max_file_bytes=None, cache=True):
588 """
588 """
589 retrieve single node from commit
589 retrieve single node from commit
590 """
590 """
591 try:
591 try:
592
592
593 _repo = self._get_repo(repo_name)
593 _repo = self._get_repo(repo_name)
594 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
594 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
595
595
596 file_node = commit.get_node(file_path)
596 file_node = commit.get_node(file_path)
597 if file_node.is_dir():
597 if file_node.is_dir():
598 raise RepositoryError('The given path is a directory')
598 raise RepositoryError('The given path is a directory')
599
599
600 _content = None
600 _content = None
601 f_name = file_node.unicode_path
601 f_name = file_node.unicode_path
602
602
603 file_data = {
603 file_data = {
604 "name": h.escape(f_name),
604 "name": h.escape(f_name),
605 "type": "file",
605 "type": "file",
606 }
606 }
607
607
608 if extended_info:
608 if extended_info:
609 file_data.update({
609 file_data.update({
610 "extension": file_node.extension,
610 "extension": file_node.extension,
611 "mimetype": file_node.mimetype,
611 "mimetype": file_node.mimetype,
612 })
612 })
613
613
614 if cache:
614 if cache:
615 md5 = file_node.md5
615 md5 = file_node.md5
616 is_binary = file_node.is_binary
616 is_binary = file_node.is_binary
617 size = file_node.size
617 size = file_node.size
618 else:
618 else:
619 is_binary, md5, size, _content = file_node.metadata_uncached()
619 is_binary, md5, size, _content = file_node.metadata_uncached()
620
620
621 file_data.update({
621 file_data.update({
622 "md5": md5,
622 "md5": md5,
623 "binary": is_binary,
623 "binary": is_binary,
624 "size": size,
624 "size": size,
625 })
625 })
626
626
627 if content and cache:
627 if content and cache:
628 # get content + cache
628 # get content + cache
629 size = file_node.size
629 size = file_node.size
630 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
630 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
631 full_content = None
631 full_content = None
632 if not file_node.is_binary and not over_size_limit:
632 if not file_node.is_binary and not over_size_limit:
633 full_content = safe_unicode(file_node.content)
633 full_content = safe_unicode(file_node.content)
634
634
635 file_data.update({
635 file_data.update({
636 "content": full_content,
636 "content": full_content,
637 })
637 })
638 elif content:
638 elif content:
639 # get content *without* cache
639 # get content *without* cache
640 if _content is None:
640 if _content is None:
641 is_binary, md5, size, _content = file_node.metadata_uncached()
641 is_binary, md5, size, _content = file_node.metadata_uncached()
642
642
643 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
643 over_size_limit = (max_file_bytes is not None and size > max_file_bytes)
644 full_content = None
644 full_content = None
645 if not is_binary and not over_size_limit:
645 if not is_binary and not over_size_limit:
646 full_content = safe_unicode(_content)
646 full_content = safe_unicode(_content)
647
647
648 file_data.update({
648 file_data.update({
649 "content": full_content,
649 "content": full_content,
650 })
650 })
651
651
652 except RepositoryError:
652 except RepositoryError:
653 log.exception("Exception in get_node")
653 log.exception("Exception in get_node")
654 raise
654 raise
655
655
656 return file_data
656 return file_data
657
657
658 def get_fts_data(self, repo_name, commit_id, root_path='/'):
658 def get_fts_data(self, repo_name, commit_id, root_path='/'):
659 """
659 """
660 Fetch node tree for usage in full text search
660 Fetch node tree for usage in full text search
661 """
661 """
662
662
663 tree_info = list()
663 tree_info = list()
664
664
665 try:
665 try:
666 _repo = self._get_repo(repo_name)
666 _repo = self._get_repo(repo_name)
667 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
667 commit = _repo.scm_instance().get_commit(commit_id=commit_id)
668 root_path = root_path.lstrip('/')
668 root_path = root_path.lstrip('/')
669 for __, dirs, files in commit.walk(root_path):
669 for __, dirs, files in commit.walk(root_path):
670
670
671 for f in files:
671 for f in files:
672 is_binary, md5, size, _content = f.metadata_uncached()
672 is_binary, md5, size, _content = f.metadata_uncached()
673 _data = {
673 _data = {
674 "name": f.unicode_path,
674 "name": f.unicode_path,
675 "md5": md5,
675 "md5": md5,
676 "extension": f.extension,
676 "extension": f.extension,
677 "binary": is_binary,
677 "binary": is_binary,
678 "size": size
678 "size": size
679 }
679 }
680
680
681 tree_info.append(_data)
681 tree_info.append(_data)
682
682
683 except RepositoryError:
683 except RepositoryError:
684 log.exception("Exception in get_nodes")
684 log.exception("Exception in get_nodes")
685 raise
685 raise
686
686
687 return tree_info
687 return tree_info
688
688
689 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
689 def create_nodes(self, user, repo, message, nodes, parent_commit=None,
690 author=None, trigger_push_hook=True):
690 author=None, trigger_push_hook=True):
691 """
691 """
692 Commits given multiple nodes into repo
692 Commits given multiple nodes into repo
693
693
694 :param user: RhodeCode User object or user_id, the commiter
694 :param user: RhodeCode User object or user_id, the commiter
695 :param repo: RhodeCode Repository object
695 :param repo: RhodeCode Repository object
696 :param message: commit message
696 :param message: commit message
697 :param nodes: mapping {filename:{'content':content},...}
697 :param nodes: mapping {filename:{'content':content},...}
698 :param parent_commit: parent commit, can be empty than it's
698 :param parent_commit: parent commit, can be empty than it's
699 initial commit
699 initial commit
700 :param author: author of commit, cna be different that commiter
700 :param author: author of commit, cna be different that commiter
701 only for git
701 only for git
702 :param trigger_push_hook: trigger push hooks
702 :param trigger_push_hook: trigger push hooks
703
703
704 :returns: new commited commit
704 :returns: new commited commit
705 """
705 """
706
706
707 user = self._get_user(user)
707 user = self._get_user(user)
708 scm_instance = repo.scm_instance(cache=False)
708 scm_instance = repo.scm_instance(cache=False)
709
709
710 processed_nodes = []
710 processed_nodes = []
711 for f_path in nodes:
711 for f_path in nodes:
712 f_path = self._sanitize_path(f_path)
712 f_path = self._sanitize_path(f_path)
713 content = nodes[f_path]['content']
713 content = nodes[f_path]['content']
714 f_path = safe_str(f_path)
714 f_path = safe_str(f_path)
715 # decoding here will force that we have proper encoded values
715 # decoding here will force that we have proper encoded values
716 # in any other case this will throw exceptions and deny commit
716 # in any other case this will throw exceptions and deny commit
717 if isinstance(content, (basestring,)):
717 if isinstance(content, (basestring,)):
718 content = safe_str(content)
718 content = safe_str(content)
719 elif isinstance(content, (file, cStringIO.OutputType,)):
719 elif isinstance(content, (file, cStringIO.OutputType,)):
720 content = content.read()
720 content = content.read()
721 else:
721 else:
722 raise Exception('Content is of unrecognized type %s' % (
722 raise Exception('Content is of unrecognized type %s' % (
723 type(content)
723 type(content)
724 ))
724 ))
725 processed_nodes.append((f_path, content))
725 processed_nodes.append((f_path, content))
726
726
727 message = safe_unicode(message)
727 message = safe_unicode(message)
728 commiter = user.full_contact
728 commiter = user.full_contact
729 author = safe_unicode(author) if author else commiter
729 author = safe_unicode(author) if author else commiter
730
730
731 imc = scm_instance.in_memory_commit
731 imc = scm_instance.in_memory_commit
732
732
733 if not parent_commit:
733 if not parent_commit:
734 parent_commit = EmptyCommit(alias=scm_instance.alias)
734 parent_commit = EmptyCommit(alias=scm_instance.alias)
735
735
736 if isinstance(parent_commit, EmptyCommit):
736 if isinstance(parent_commit, EmptyCommit):
737 # EmptyCommit means we we're editing empty repository
737 # EmptyCommit means we we're editing empty repository
738 parents = None
738 parents = None
739 else:
739 else:
740 parents = [parent_commit]
740 parents = [parent_commit]
741 # add multiple nodes
741 # add multiple nodes
742 for path, content in processed_nodes:
742 for path, content in processed_nodes:
743 imc.add(FileNode(path, content=content))
743 imc.add(FileNode(path, content=content))
744 # TODO: handle pre push scenario
744 # TODO: handle pre push scenario
745 tip = imc.commit(message=message,
745 tip = imc.commit(message=message,
746 author=author,
746 author=author,
747 parents=parents,
747 parents=parents,
748 branch=parent_commit.branch)
748 branch=parent_commit.branch)
749
749
750 self.mark_for_invalidation(repo.repo_name)
750 self.mark_for_invalidation(repo.repo_name)
751 if trigger_push_hook:
751 if trigger_push_hook:
752 hooks_utils.trigger_post_push_hook(
752 hooks_utils.trigger_post_push_hook(
753 username=user.username, action='push_local',
753 username=user.username, action='push_local',
754 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
754 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
755 hook_type='post_push',
755 hook_type='post_push',
756 commit_ids=[tip.raw_id])
756 commit_ids=[tip.raw_id])
757 return tip
757 return tip
758
758
759 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
759 def update_nodes(self, user, repo, message, nodes, parent_commit=None,
760 author=None, trigger_push_hook=True):
760 author=None, trigger_push_hook=True):
761 user = self._get_user(user)
761 user = self._get_user(user)
762 scm_instance = repo.scm_instance(cache=False)
762 scm_instance = repo.scm_instance(cache=False)
763
763
764 message = safe_unicode(message)
764 message = safe_unicode(message)
765 commiter = user.full_contact
765 commiter = user.full_contact
766 author = safe_unicode(author) if author else commiter
766 author = safe_unicode(author) if author else commiter
767
767
768 imc = scm_instance.in_memory_commit
768 imc = scm_instance.in_memory_commit
769
769
770 if not parent_commit:
770 if not parent_commit:
771 parent_commit = EmptyCommit(alias=scm_instance.alias)
771 parent_commit = EmptyCommit(alias=scm_instance.alias)
772
772
773 if isinstance(parent_commit, EmptyCommit):
773 if isinstance(parent_commit, EmptyCommit):
774 # EmptyCommit means we we're editing empty repository
774 # EmptyCommit means we we're editing empty repository
775 parents = None
775 parents = None
776 else:
776 else:
777 parents = [parent_commit]
777 parents = [parent_commit]
778
778
779 # add multiple nodes
779 # add multiple nodes
780 for _filename, data in nodes.items():
780 for _filename, data in nodes.items():
781 # new filename, can be renamed from the old one, also sanitaze
781 # new filename, can be renamed from the old one, also sanitaze
782 # the path for any hack around relative paths like ../../ etc.
782 # the path for any hack around relative paths like ../../ etc.
783 filename = self._sanitize_path(data['filename'])
783 filename = self._sanitize_path(data['filename'])
784 old_filename = self._sanitize_path(_filename)
784 old_filename = self._sanitize_path(_filename)
785 content = data['content']
785 content = data['content']
786 file_mode = data.get('mode')
786 file_mode = data.get('mode')
787 filenode = FileNode(old_filename, content=content, mode=file_mode)
787 filenode = FileNode(old_filename, content=content, mode=file_mode)
788 op = data['op']
788 op = data['op']
789 if op == 'add':
789 if op == 'add':
790 imc.add(filenode)
790 imc.add(filenode)
791 elif op == 'del':
791 elif op == 'del':
792 imc.remove(filenode)
792 imc.remove(filenode)
793 elif op == 'mod':
793 elif op == 'mod':
794 if filename != old_filename:
794 if filename != old_filename:
795 # TODO: handle renames more efficient, needs vcs lib changes
795 # TODO: handle renames more efficient, needs vcs lib changes
796 imc.remove(filenode)
796 imc.remove(filenode)
797 imc.add(FileNode(filename, content=content, mode=file_mode))
797 imc.add(FileNode(filename, content=content, mode=file_mode))
798 else:
798 else:
799 imc.change(filenode)
799 imc.change(filenode)
800
800
801 try:
801 try:
802 # TODO: handle pre push scenario commit changes
802 # TODO: handle pre push scenario commit changes
803 tip = imc.commit(message=message,
803 tip = imc.commit(message=message,
804 author=author,
804 author=author,
805 parents=parents,
805 parents=parents,
806 branch=parent_commit.branch)
806 branch=parent_commit.branch)
807 except NodeNotChangedError:
807 except NodeNotChangedError:
808 raise
808 raise
809 except Exception as e:
809 except Exception as e:
810 log.exception("Unexpected exception during call to imc.commit")
810 log.exception("Unexpected exception during call to imc.commit")
811 raise IMCCommitError(str(e))
811 raise IMCCommitError(str(e))
812 finally:
812 finally:
813 # always clear caches, if commit fails we want fresh object also
813 # always clear caches, if commit fails we want fresh object also
814 self.mark_for_invalidation(repo.repo_name)
814 self.mark_for_invalidation(repo.repo_name)
815
815
816 if trigger_push_hook:
816 if trigger_push_hook:
817 hooks_utils.trigger_post_push_hook(
817 hooks_utils.trigger_post_push_hook(
818 username=user.username, action='push_local', hook_type='post_push',
818 username=user.username, action='push_local', hook_type='post_push',
819 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
819 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
820 commit_ids=[tip.raw_id])
820 commit_ids=[tip.raw_id])
821
821
822 return tip
822 return tip
823
823
824 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
824 def delete_nodes(self, user, repo, message, nodes, parent_commit=None,
825 author=None, trigger_push_hook=True):
825 author=None, trigger_push_hook=True):
826 """
826 """
827 Deletes given multiple nodes into `repo`
827 Deletes given multiple nodes into `repo`
828
828
829 :param user: RhodeCode User object or user_id, the committer
829 :param user: RhodeCode User object or user_id, the committer
830 :param repo: RhodeCode Repository object
830 :param repo: RhodeCode Repository object
831 :param message: commit message
831 :param message: commit message
832 :param nodes: mapping {filename:{'content':content},...}
832 :param nodes: mapping {filename:{'content':content},...}
833 :param parent_commit: parent commit, can be empty than it's initial
833 :param parent_commit: parent commit, can be empty than it's initial
834 commit
834 commit
835 :param author: author of commit, cna be different that commiter only
835 :param author: author of commit, cna be different that commiter only
836 for git
836 for git
837 :param trigger_push_hook: trigger push hooks
837 :param trigger_push_hook: trigger push hooks
838
838
839 :returns: new commit after deletion
839 :returns: new commit after deletion
840 """
840 """
841
841
842 user = self._get_user(user)
842 user = self._get_user(user)
843 scm_instance = repo.scm_instance(cache=False)
843 scm_instance = repo.scm_instance(cache=False)
844
844
845 processed_nodes = []
845 processed_nodes = []
846 for f_path in nodes:
846 for f_path in nodes:
847 f_path = self._sanitize_path(f_path)
847 f_path = self._sanitize_path(f_path)
848 # content can be empty but for compatabilty it allows same dicts
848 # content can be empty but for compatabilty it allows same dicts
849 # structure as add_nodes
849 # structure as add_nodes
850 content = nodes[f_path].get('content')
850 content = nodes[f_path].get('content')
851 processed_nodes.append((f_path, content))
851 processed_nodes.append((f_path, content))
852
852
853 message = safe_unicode(message)
853 message = safe_unicode(message)
854 commiter = user.full_contact
854 commiter = user.full_contact
855 author = safe_unicode(author) if author else commiter
855 author = safe_unicode(author) if author else commiter
856
856
857 imc = scm_instance.in_memory_commit
857 imc = scm_instance.in_memory_commit
858
858
859 if not parent_commit:
859 if not parent_commit:
860 parent_commit = EmptyCommit(alias=scm_instance.alias)
860 parent_commit = EmptyCommit(alias=scm_instance.alias)
861
861
862 if isinstance(parent_commit, EmptyCommit):
862 if isinstance(parent_commit, EmptyCommit):
863 # EmptyCommit means we we're editing empty repository
863 # EmptyCommit means we we're editing empty repository
864 parents = None
864 parents = None
865 else:
865 else:
866 parents = [parent_commit]
866 parents = [parent_commit]
867 # add multiple nodes
867 # add multiple nodes
868 for path, content in processed_nodes:
868 for path, content in processed_nodes:
869 imc.remove(FileNode(path, content=content))
869 imc.remove(FileNode(path, content=content))
870
870
871 # TODO: handle pre push scenario
871 # TODO: handle pre push scenario
872 tip = imc.commit(message=message,
872 tip = imc.commit(message=message,
873 author=author,
873 author=author,
874 parents=parents,
874 parents=parents,
875 branch=parent_commit.branch)
875 branch=parent_commit.branch)
876
876
877 self.mark_for_invalidation(repo.repo_name)
877 self.mark_for_invalidation(repo.repo_name)
878 if trigger_push_hook:
878 if trigger_push_hook:
879 hooks_utils.trigger_post_push_hook(
879 hooks_utils.trigger_post_push_hook(
880 username=user.username, action='push_local', hook_type='post_push',
880 username=user.username, action='push_local', hook_type='post_push',
881 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
881 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
882 commit_ids=[tip.raw_id])
882 commit_ids=[tip.raw_id])
883 return tip
883 return tip
884
884
885 def strip(self, repo, commit_id, branch):
885 def strip(self, repo, commit_id, branch):
886 scm_instance = repo.scm_instance(cache=False)
886 scm_instance = repo.scm_instance(cache=False)
887 scm_instance.config.clear_section('hooks')
887 scm_instance.config.clear_section('hooks')
888 scm_instance.strip(commit_id, branch)
888 scm_instance.strip(commit_id, branch)
889 self.mark_for_invalidation(repo.repo_name)
889 self.mark_for_invalidation(repo.repo_name)
890
890
891 def get_unread_journal(self):
891 def get_unread_journal(self):
892 return self.sa.query(UserLog).count()
892 return self.sa.query(UserLog).count()
893
893
894 @classmethod
895 def backend_landing_ref(cls, repo_type):
896 """
897 Return a default landing ref based on a repository type.
898 """
899
900 landing_ref = {
901 'hg': ('branch:default', 'default'),
902 'git': ('branch:master', 'master'),
903 'svn': ('rev:tip', 'latest tip'),
904 'default': ('rev:tip', 'latest tip'),
905 }
906
907 return landing_ref.get(repo_type) or landing_ref['default']
908
894 def get_repo_landing_revs(self, translator, repo=None):
909 def get_repo_landing_revs(self, translator, repo=None):
895 """
910 """
896 Generates select option with tags branches and bookmarks (for hg only)
911 Generates select option with tags branches and bookmarks (for hg only)
897 grouped by type
912 grouped by type
898
913
899 :param repo:
914 :param repo:
900 """
915 """
901 _ = translator
916 _ = translator
902 repo = self._get_repo(repo)
917 repo = self._get_repo(repo)
903
918
904 hist_l = [
919 if repo:
905 ['rev:tip', _('latest tip')]
920 repo_type = repo.repo_type
921 else:
922 repo_type = 'default'
923
924 default_landing_ref, landing_ref_lbl = self.backend_landing_ref(repo_type)
925
926 default_ref_options = [
927 [default_landing_ref, landing_ref_lbl]
906 ]
928 ]
907 choices = [
929 default_choices = [
908 'rev:tip'
930 default_landing_ref
909 ]
931 ]
910
932
911 if not repo:
933 if not repo:
912 return choices, hist_l
934 return default_choices, default_ref_options
913
935
914 repo = repo.scm_instance()
936 repo = repo.scm_instance()
915
937
916 branches_group = (
938 ref_options = [('rev:tip', 'latest tip')]
917 [(u'branch:%s' % safe_unicode(b), safe_unicode(b))
939 choices = ['rev:tip']
918 for b in repo.branches],
940
919 _("Branches"))
941 # branches
920 hist_l.append(branches_group)
942 branch_group = [(u'branch:%s' % safe_unicode(b), safe_unicode(b)) for b in repo.branches]
943 if not branch_group:
944 # new repo, or without maybe a branch?
945 branch_group = default_ref_options
946
947 branches_group = (branch_group, _("Branches"))
948 ref_options.append(branches_group)
921 choices.extend([x[0] for x in branches_group[0]])
949 choices.extend([x[0] for x in branches_group[0]])
922
950
951 # bookmarks for HG
923 if repo.alias == 'hg':
952 if repo.alias == 'hg':
924 bookmarks_group = (
953 bookmarks_group = (
925 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
954 [(u'book:%s' % safe_unicode(b), safe_unicode(b))
926 for b in repo.bookmarks],
955 for b in repo.bookmarks],
927 _("Bookmarks"))
956 _("Bookmarks"))
928 hist_l.append(bookmarks_group)
957 ref_options.append(bookmarks_group)
929 choices.extend([x[0] for x in bookmarks_group[0]])
958 choices.extend([x[0] for x in bookmarks_group[0]])
930
959
960 # tags
931 tags_group = (
961 tags_group = (
932 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
962 [(u'tag:%s' % safe_unicode(t), safe_unicode(t))
933 for t in repo.tags],
963 for t in repo.tags],
934 _("Tags"))
964 _("Tags"))
935 hist_l.append(tags_group)
965 ref_options.append(tags_group)
936 choices.extend([x[0] for x in tags_group[0]])
966 choices.extend([x[0] for x in tags_group[0]])
937
967
938 return choices, hist_l
968 return choices, ref_options
939
969
940 def get_server_info(self, environ=None):
970 def get_server_info(self, environ=None):
941 server_info = get_system_info(environ)
971 server_info = get_system_info(environ)
942 return server_info
972 return server_info
@@ -1,430 +1,441 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 colander
21 import colander
22 import deform.widget
22 import deform.widget
23
23
24 from rhodecode.translation import _
24 from rhodecode.translation import _
25 from rhodecode.model.validation_schema.utils import convert_to_optgroup
25 from rhodecode.model.validation_schema.utils import convert_to_optgroup
26 from rhodecode.model.validation_schema import validators, preparers, types
26 from rhodecode.model.validation_schema import validators, preparers, types
27
27
28 DEFAULT_LANDING_REF = 'rev:tip'
28 DEFAULT_LANDING_REF = 'rev:tip'
29 DEFAULT_BACKEND_LANDING_REF = {
30 'hg': 'branch:default',
31 'git': 'branch:master',
32 'svn': 'rev:tip',
33 }
29
34
30
35
31 def get_group_and_repo(repo_name):
36 def get_group_and_repo(repo_name):
32 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.repo_group import RepoGroupModel
33 return RepoGroupModel()._get_group_name_and_parent(
38 return RepoGroupModel()._get_group_name_and_parent(
34 repo_name, get_object=True)
39 repo_name, get_object=True)
35
40
36
41
37 def get_repo_group(repo_group_id):
42 def get_repo_group(repo_group_id):
38 from rhodecode.model.repo_group import RepoGroup
43 from rhodecode.model.repo_group import RepoGroup
39 return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
44 return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR
40
45
41
46
42 @colander.deferred
47 @colander.deferred
43 def deferred_repo_type_validator(node, kw):
48 def deferred_repo_type_validator(node, kw):
44 options = kw.get('repo_type_options', [])
49 options = kw.get('repo_type_options', [])
45 return colander.OneOf([x for x in options])
50 return colander.OneOf([x for x in options])
46
51
47
52
48 @colander.deferred
53 @colander.deferred
49 def deferred_repo_owner_validator(node, kw):
54 def deferred_repo_owner_validator(node, kw):
50
55
51 def repo_owner_validator(node, value):
56 def repo_owner_validator(node, value):
52 from rhodecode.model.db import User
57 from rhodecode.model.db import User
53 existing = User.get_by_username(value)
58 existing = User.get_by_username(value)
54 if not existing:
59 if not existing:
55 msg = _(u'Repo owner with id `{}` does not exists').format(value)
60 msg = _(u'Repo owner with id `{}` does not exists').format(value)
56 raise colander.Invalid(node, msg)
61 raise colander.Invalid(node, msg)
57
62
58 return repo_owner_validator
63 return repo_owner_validator
59
64
60
65
61 @colander.deferred
66 @colander.deferred
62 def deferred_landing_ref_validator(node, kw):
67 def deferred_landing_ref_validator(node, kw):
63 options = kw.get(
68 options = kw.get(
64 'repo_ref_options', [DEFAULT_LANDING_REF])
69 'repo_ref_options', [DEFAULT_LANDING_REF])
65 return colander.OneOf([x for x in options])
70 return colander.OneOf([x for x in options])
66
71
67
72
68 @colander.deferred
73 @colander.deferred
69 def deferred_sync_uri_validator(node, kw):
74 def deferred_sync_uri_validator(node, kw):
70 repo_type = kw.get('repo_type')
75 repo_type = kw.get('repo_type')
71 validator = validators.CloneUriValidator(repo_type)
76 validator = validators.CloneUriValidator(repo_type)
72 return validator
77 return validator
73
78
74
79
75 @colander.deferred
80 @colander.deferred
76 def deferred_landing_ref_widget(node, kw):
81 def deferred_landing_ref_widget(node, kw):
77 items = kw.get(
82 repo_type = kw.get('repo_type')
78 'repo_ref_items', [(DEFAULT_LANDING_REF, DEFAULT_LANDING_REF)])
83 default_opts = []
84 if repo_type:
85 default_opts.append(
86 (DEFAULT_BACKEND_LANDING_REF[repo_type],
87 DEFAULT_BACKEND_LANDING_REF[repo_type]))
88
89 items = kw.get('repo_ref_items', default_opts)
79 items = convert_to_optgroup(items)
90 items = convert_to_optgroup(items)
80 return deform.widget.Select2Widget(values=items)
91 return deform.widget.Select2Widget(values=items)
81
92
82
93
83 @colander.deferred
94 @colander.deferred
84 def deferred_fork_of_validator(node, kw):
95 def deferred_fork_of_validator(node, kw):
85 old_values = kw.get('old_values') or {}
96 old_values = kw.get('old_values') or {}
86
97
87 def fork_of_validator(node, value):
98 def fork_of_validator(node, value):
88 from rhodecode.model.db import Repository, RepoGroup
99 from rhodecode.model.db import Repository, RepoGroup
89 existing = Repository.get_by_repo_name(value)
100 existing = Repository.get_by_repo_name(value)
90 if not existing:
101 if not existing:
91 msg = _(u'Fork with id `{}` does not exists').format(value)
102 msg = _(u'Fork with id `{}` does not exists').format(value)
92 raise colander.Invalid(node, msg)
103 raise colander.Invalid(node, msg)
93 elif old_values['repo_name'] == existing.repo_name:
104 elif old_values['repo_name'] == existing.repo_name:
94 msg = _(u'Cannot set fork of '
105 msg = _(u'Cannot set fork of '
95 u'parameter of this repository to itself').format(value)
106 u'parameter of this repository to itself').format(value)
96 raise colander.Invalid(node, msg)
107 raise colander.Invalid(node, msg)
97
108
98 return fork_of_validator
109 return fork_of_validator
99
110
100
111
101 @colander.deferred
112 @colander.deferred
102 def deferred_can_write_to_group_validator(node, kw):
113 def deferred_can_write_to_group_validator(node, kw):
103 request_user = kw.get('user')
114 request_user = kw.get('user')
104 old_values = kw.get('old_values') or {}
115 old_values = kw.get('old_values') or {}
105
116
106 def can_write_to_group_validator(node, value):
117 def can_write_to_group_validator(node, value):
107 """
118 """
108 Checks if given repo path is writable by user. This includes checks if
119 Checks if given repo path is writable by user. This includes checks if
109 user is allowed to create repositories under root path or under
120 user is allowed to create repositories under root path or under
110 repo group paths
121 repo group paths
111 """
122 """
112
123
113 from rhodecode.lib.auth import (
124 from rhodecode.lib.auth import (
114 HasPermissionAny, HasRepoGroupPermissionAny)
125 HasPermissionAny, HasRepoGroupPermissionAny)
115 from rhodecode.model.repo_group import RepoGroupModel
126 from rhodecode.model.repo_group import RepoGroupModel
116
127
117 messages = {
128 messages = {
118 'invalid_repo_group':
129 'invalid_repo_group':
119 _(u"Repository group `{}` does not exist"),
130 _(u"Repository group `{}` does not exist"),
120 # permissions denied we expose as not existing, to prevent
131 # permissions denied we expose as not existing, to prevent
121 # resource discovery
132 # resource discovery
122 'permission_denied':
133 'permission_denied':
123 _(u"Repository group `{}` does not exist"),
134 _(u"Repository group `{}` does not exist"),
124 'permission_denied_root':
135 'permission_denied_root':
125 _(u"You do not have the permission to store "
136 _(u"You do not have the permission to store "
126 u"repositories in the root location.")
137 u"repositories in the root location.")
127 }
138 }
128
139
129 value = value['repo_group_name']
140 value = value['repo_group_name']
130
141
131 is_root_location = value is types.RootLocation
142 is_root_location = value is types.RootLocation
132 # NOT initialized validators, we must call them
143 # NOT initialized validators, we must call them
133 can_create_repos_at_root = HasPermissionAny(
144 can_create_repos_at_root = HasPermissionAny(
134 'hg.admin', 'hg.create.repository')
145 'hg.admin', 'hg.create.repository')
135
146
136 # if values is root location, we simply need to check if we can write
147 # if values is root location, we simply need to check if we can write
137 # to root location !
148 # to root location !
138 if is_root_location:
149 if is_root_location:
139 if can_create_repos_at_root(user=request_user):
150 if can_create_repos_at_root(user=request_user):
140 # we can create repo group inside tool-level. No more checks
151 # we can create repo group inside tool-level. No more checks
141 # are required
152 # are required
142 return
153 return
143 else:
154 else:
144 # "fake" node name as repo_name, otherwise we oddly report
155 # "fake" node name as repo_name, otherwise we oddly report
145 # the error as if it was coming form repo_group
156 # the error as if it was coming form repo_group
146 # however repo_group is empty when using root location.
157 # however repo_group is empty when using root location.
147 node.name = 'repo_name'
158 node.name = 'repo_name'
148 raise colander.Invalid(node, messages['permission_denied_root'])
159 raise colander.Invalid(node, messages['permission_denied_root'])
149
160
150 # parent group not exists ? throw an error
161 # parent group not exists ? throw an error
151 repo_group = RepoGroupModel().get_by_group_name(value)
162 repo_group = RepoGroupModel().get_by_group_name(value)
152 if value and not repo_group:
163 if value and not repo_group:
153 raise colander.Invalid(
164 raise colander.Invalid(
154 node, messages['invalid_repo_group'].format(value))
165 node, messages['invalid_repo_group'].format(value))
155
166
156 gr_name = repo_group.group_name
167 gr_name = repo_group.group_name
157
168
158 # create repositories with write permission on group is set to true
169 # create repositories with write permission on group is set to true
159 create_on_write = HasPermissionAny(
170 create_on_write = HasPermissionAny(
160 'hg.create.write_on_repogroup.true')(user=request_user)
171 'hg.create.write_on_repogroup.true')(user=request_user)
161
172
162 group_admin = HasRepoGroupPermissionAny('group.admin')(
173 group_admin = HasRepoGroupPermissionAny('group.admin')(
163 gr_name, 'can write into group validator', user=request_user)
174 gr_name, 'can write into group validator', user=request_user)
164 group_write = HasRepoGroupPermissionAny('group.write')(
175 group_write = HasRepoGroupPermissionAny('group.write')(
165 gr_name, 'can write into group validator', user=request_user)
176 gr_name, 'can write into group validator', user=request_user)
166
177
167 forbidden = not (group_admin or (group_write and create_on_write))
178 forbidden = not (group_admin or (group_write and create_on_write))
168
179
169 # TODO: handling of old values, and detecting no-change in path
180 # TODO: handling of old values, and detecting no-change in path
170 # to skip permission checks in such cases. This only needs to be
181 # to skip permission checks in such cases. This only needs to be
171 # implemented if we use this schema in forms as well
182 # implemented if we use this schema in forms as well
172
183
173 # gid = (old_data['repo_group'].get('group_id')
184 # gid = (old_data['repo_group'].get('group_id')
174 # if (old_data and 'repo_group' in old_data) else None)
185 # if (old_data and 'repo_group' in old_data) else None)
175 # value_changed = gid != safe_int(value)
186 # value_changed = gid != safe_int(value)
176 # new = not old_data
187 # new = not old_data
177
188
178 # do check if we changed the value, there's a case that someone got
189 # do check if we changed the value, there's a case that someone got
179 # revoked write permissions to a repository, he still created, we
190 # revoked write permissions to a repository, he still created, we
180 # don't need to check permission if he didn't change the value of
191 # don't need to check permission if he didn't change the value of
181 # groups in form box
192 # groups in form box
182 # if value_changed or new:
193 # if value_changed or new:
183 # # parent group need to be existing
194 # # parent group need to be existing
184 # TODO: ENDS HERE
195 # TODO: ENDS HERE
185
196
186 if repo_group and forbidden:
197 if repo_group and forbidden:
187 msg = messages['permission_denied'].format(value)
198 msg = messages['permission_denied'].format(value)
188 raise colander.Invalid(node, msg)
199 raise colander.Invalid(node, msg)
189
200
190 return can_write_to_group_validator
201 return can_write_to_group_validator
191
202
192
203
193 @colander.deferred
204 @colander.deferred
194 def deferred_unique_name_validator(node, kw):
205 def deferred_unique_name_validator(node, kw):
195 request_user = kw.get('user')
206 request_user = kw.get('user')
196 old_values = kw.get('old_values') or {}
207 old_values = kw.get('old_values') or {}
197
208
198 def unique_name_validator(node, value):
209 def unique_name_validator(node, value):
199 from rhodecode.model.db import Repository, RepoGroup
210 from rhodecode.model.db import Repository, RepoGroup
200 name_changed = value != old_values.get('repo_name')
211 name_changed = value != old_values.get('repo_name')
201
212
202 existing = Repository.get_by_repo_name(value)
213 existing = Repository.get_by_repo_name(value)
203 if name_changed and existing:
214 if name_changed and existing:
204 msg = _(u'Repository with name `{}` already exists').format(value)
215 msg = _(u'Repository with name `{}` already exists').format(value)
205 raise colander.Invalid(node, msg)
216 raise colander.Invalid(node, msg)
206
217
207 existing_group = RepoGroup.get_by_group_name(value)
218 existing_group = RepoGroup.get_by_group_name(value)
208 if name_changed and existing_group:
219 if name_changed and existing_group:
209 msg = _(u'Repository group with name `{}` already exists').format(
220 msg = _(u'Repository group with name `{}` already exists').format(
210 value)
221 value)
211 raise colander.Invalid(node, msg)
222 raise colander.Invalid(node, msg)
212 return unique_name_validator
223 return unique_name_validator
213
224
214
225
215 @colander.deferred
226 @colander.deferred
216 def deferred_repo_name_validator(node, kw):
227 def deferred_repo_name_validator(node, kw):
217 def no_git_suffix_validator(node, value):
228 def no_git_suffix_validator(node, value):
218 if value.endswith('.git'):
229 if value.endswith('.git'):
219 msg = _('Repository name cannot end with .git')
230 msg = _('Repository name cannot end with .git')
220 raise colander.Invalid(node, msg)
231 raise colander.Invalid(node, msg)
221 return colander.All(
232 return colander.All(
222 no_git_suffix_validator, validators.valid_name_validator)
233 no_git_suffix_validator, validators.valid_name_validator)
223
234
224
235
225 @colander.deferred
236 @colander.deferred
226 def deferred_repo_group_validator(node, kw):
237 def deferred_repo_group_validator(node, kw):
227 options = kw.get(
238 options = kw.get(
228 'repo_repo_group_options')
239 'repo_repo_group_options')
229 return colander.OneOf([x for x in options])
240 return colander.OneOf([x for x in options])
230
241
231
242
232 @colander.deferred
243 @colander.deferred
233 def deferred_repo_group_widget(node, kw):
244 def deferred_repo_group_widget(node, kw):
234 items = kw.get('repo_repo_group_items')
245 items = kw.get('repo_repo_group_items')
235 return deform.widget.Select2Widget(values=items)
246 return deform.widget.Select2Widget(values=items)
236
247
237
248
238 class GroupType(colander.Mapping):
249 class GroupType(colander.Mapping):
239 def _validate(self, node, value):
250 def _validate(self, node, value):
240 try:
251 try:
241 return dict(repo_group_name=value)
252 return dict(repo_group_name=value)
242 except Exception as e:
253 except Exception as e:
243 raise colander.Invalid(
254 raise colander.Invalid(
244 node, '"${val}" is not a mapping type: ${err}'.format(
255 node, '"${val}" is not a mapping type: ${err}'.format(
245 val=value, err=e))
256 val=value, err=e))
246
257
247 def deserialize(self, node, cstruct):
258 def deserialize(self, node, cstruct):
248 if cstruct is colander.null:
259 if cstruct is colander.null:
249 return cstruct
260 return cstruct
250
261
251 appstruct = super(GroupType, self).deserialize(node, cstruct)
262 appstruct = super(GroupType, self).deserialize(node, cstruct)
252 validated_name = appstruct['repo_group_name']
263 validated_name = appstruct['repo_group_name']
253
264
254 # inject group based on once deserialized data
265 # inject group based on once deserialized data
255 (repo_name_without_group,
266 (repo_name_without_group,
256 parent_group_name,
267 parent_group_name,
257 parent_group) = get_group_and_repo(validated_name)
268 parent_group) = get_group_and_repo(validated_name)
258
269
259 appstruct['repo_name_with_group'] = validated_name
270 appstruct['repo_name_with_group'] = validated_name
260 appstruct['repo_name_without_group'] = repo_name_without_group
271 appstruct['repo_name_without_group'] = repo_name_without_group
261 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
272 appstruct['repo_group_name'] = parent_group_name or types.RootLocation
262
273
263 if parent_group:
274 if parent_group:
264 appstruct['repo_group_id'] = parent_group.group_id
275 appstruct['repo_group_id'] = parent_group.group_id
265
276
266 return appstruct
277 return appstruct
267
278
268
279
269 class GroupSchema(colander.SchemaNode):
280 class GroupSchema(colander.SchemaNode):
270 schema_type = GroupType
281 schema_type = GroupType
271 validator = deferred_can_write_to_group_validator
282 validator = deferred_can_write_to_group_validator
272 missing = colander.null
283 missing = colander.null
273
284
274
285
275 class RepoGroup(GroupSchema):
286 class RepoGroup(GroupSchema):
276 repo_group_name = colander.SchemaNode(
287 repo_group_name = colander.SchemaNode(
277 types.GroupNameType())
288 types.GroupNameType())
278 repo_group_id = colander.SchemaNode(
289 repo_group_id = colander.SchemaNode(
279 colander.String(), missing=None)
290 colander.String(), missing=None)
280 repo_name_without_group = colander.SchemaNode(
291 repo_name_without_group = colander.SchemaNode(
281 colander.String(), missing=None)
292 colander.String(), missing=None)
282
293
283
294
284 class RepoGroupAccessSchema(colander.MappingSchema):
295 class RepoGroupAccessSchema(colander.MappingSchema):
285 repo_group = RepoGroup()
296 repo_group = RepoGroup()
286
297
287
298
288 class RepoNameUniqueSchema(colander.MappingSchema):
299 class RepoNameUniqueSchema(colander.MappingSchema):
289 unique_repo_name = colander.SchemaNode(
300 unique_repo_name = colander.SchemaNode(
290 colander.String(),
301 colander.String(),
291 validator=deferred_unique_name_validator)
302 validator=deferred_unique_name_validator)
292
303
293
304
294 class RepoSchema(colander.MappingSchema):
305 class RepoSchema(colander.MappingSchema):
295
306
296 repo_name = colander.SchemaNode(
307 repo_name = colander.SchemaNode(
297 types.RepoNameType(),
308 types.RepoNameType(),
298 validator=deferred_repo_name_validator)
309 validator=deferred_repo_name_validator)
299
310
300 repo_type = colander.SchemaNode(
311 repo_type = colander.SchemaNode(
301 colander.String(),
312 colander.String(),
302 validator=deferred_repo_type_validator)
313 validator=deferred_repo_type_validator)
303
314
304 repo_owner = colander.SchemaNode(
315 repo_owner = colander.SchemaNode(
305 colander.String(),
316 colander.String(),
306 validator=deferred_repo_owner_validator,
317 validator=deferred_repo_owner_validator,
307 widget=deform.widget.TextInputWidget())
318 widget=deform.widget.TextInputWidget())
308
319
309 repo_description = colander.SchemaNode(
320 repo_description = colander.SchemaNode(
310 colander.String(), missing='',
321 colander.String(), missing='',
311 widget=deform.widget.TextAreaWidget())
322 widget=deform.widget.TextAreaWidget())
312
323
313 repo_landing_commit_ref = colander.SchemaNode(
324 repo_landing_commit_ref = colander.SchemaNode(
314 colander.String(),
325 colander.String(),
315 validator=deferred_landing_ref_validator,
326 validator=deferred_landing_ref_validator,
316 preparers=[preparers.strip_preparer],
327 preparers=[preparers.strip_preparer],
317 missing=DEFAULT_LANDING_REF,
328 missing=DEFAULT_LANDING_REF,
318 widget=deferred_landing_ref_widget)
329 widget=deferred_landing_ref_widget)
319
330
320 repo_clone_uri = colander.SchemaNode(
331 repo_clone_uri = colander.SchemaNode(
321 colander.String(),
332 colander.String(),
322 validator=deferred_sync_uri_validator,
333 validator=deferred_sync_uri_validator,
323 preparers=[preparers.strip_preparer],
334 preparers=[preparers.strip_preparer],
324 missing='')
335 missing='')
325
336
326 repo_push_uri = colander.SchemaNode(
337 repo_push_uri = colander.SchemaNode(
327 colander.String(),
338 colander.String(),
328 validator=deferred_sync_uri_validator,
339 validator=deferred_sync_uri_validator,
329 preparers=[preparers.strip_preparer],
340 preparers=[preparers.strip_preparer],
330 missing='')
341 missing='')
331
342
332 repo_fork_of = colander.SchemaNode(
343 repo_fork_of = colander.SchemaNode(
333 colander.String(),
344 colander.String(),
334 validator=deferred_fork_of_validator,
345 validator=deferred_fork_of_validator,
335 missing=None)
346 missing=None)
336
347
337 repo_private = colander.SchemaNode(
348 repo_private = colander.SchemaNode(
338 types.StringBooleanType(),
349 types.StringBooleanType(),
339 missing=False, widget=deform.widget.CheckboxWidget())
350 missing=False, widget=deform.widget.CheckboxWidget())
340 repo_copy_permissions = colander.SchemaNode(
351 repo_copy_permissions = colander.SchemaNode(
341 types.StringBooleanType(),
352 types.StringBooleanType(),
342 missing=False, widget=deform.widget.CheckboxWidget())
353 missing=False, widget=deform.widget.CheckboxWidget())
343 repo_enable_statistics = colander.SchemaNode(
354 repo_enable_statistics = colander.SchemaNode(
344 types.StringBooleanType(),
355 types.StringBooleanType(),
345 missing=False, widget=deform.widget.CheckboxWidget())
356 missing=False, widget=deform.widget.CheckboxWidget())
346 repo_enable_downloads = colander.SchemaNode(
357 repo_enable_downloads = colander.SchemaNode(
347 types.StringBooleanType(),
358 types.StringBooleanType(),
348 missing=False, widget=deform.widget.CheckboxWidget())
359 missing=False, widget=deform.widget.CheckboxWidget())
349 repo_enable_locking = colander.SchemaNode(
360 repo_enable_locking = colander.SchemaNode(
350 types.StringBooleanType(),
361 types.StringBooleanType(),
351 missing=False, widget=deform.widget.CheckboxWidget())
362 missing=False, widget=deform.widget.CheckboxWidget())
352
363
353 def deserialize(self, cstruct):
364 def deserialize(self, cstruct):
354 """
365 """
355 Custom deserialize that allows to chain validation, and verify
366 Custom deserialize that allows to chain validation, and verify
356 permissions, and as last step uniqueness
367 permissions, and as last step uniqueness
357 """
368 """
358
369
359 # first pass, to validate given data
370 # first pass, to validate given data
360 appstruct = super(RepoSchema, self).deserialize(cstruct)
371 appstruct = super(RepoSchema, self).deserialize(cstruct)
361 validated_name = appstruct['repo_name']
372 validated_name = appstruct['repo_name']
362
373
363 # second pass to validate permissions to repo_group
374 # second pass to validate permissions to repo_group
364 second = RepoGroupAccessSchema().bind(**self.bindings)
375 second = RepoGroupAccessSchema().bind(**self.bindings)
365 appstruct_second = second.deserialize({'repo_group': validated_name})
376 appstruct_second = second.deserialize({'repo_group': validated_name})
366 # save result
377 # save result
367 appstruct['repo_group'] = appstruct_second['repo_group']
378 appstruct['repo_group'] = appstruct_second['repo_group']
368
379
369 # thirds to validate uniqueness
380 # thirds to validate uniqueness
370 third = RepoNameUniqueSchema().bind(**self.bindings)
381 third = RepoNameUniqueSchema().bind(**self.bindings)
371 third.deserialize({'unique_repo_name': validated_name})
382 third.deserialize({'unique_repo_name': validated_name})
372
383
373 return appstruct
384 return appstruct
374
385
375
386
376 class RepoSettingsSchema(RepoSchema):
387 class RepoSettingsSchema(RepoSchema):
377 repo_group = colander.SchemaNode(
388 repo_group = colander.SchemaNode(
378 colander.Integer(),
389 colander.Integer(),
379 validator=deferred_repo_group_validator,
390 validator=deferred_repo_group_validator,
380 widget=deferred_repo_group_widget,
391 widget=deferred_repo_group_widget,
381 missing='')
392 missing='')
382
393
383 repo_clone_uri_change = colander.SchemaNode(
394 repo_clone_uri_change = colander.SchemaNode(
384 colander.String(),
395 colander.String(),
385 missing='NEW')
396 missing='NEW')
386
397
387 repo_clone_uri = colander.SchemaNode(
398 repo_clone_uri = colander.SchemaNode(
388 colander.String(),
399 colander.String(),
389 preparers=[preparers.strip_preparer],
400 preparers=[preparers.strip_preparer],
390 validator=deferred_sync_uri_validator,
401 validator=deferred_sync_uri_validator,
391 missing='')
402 missing='')
392
403
393 repo_push_uri_change = colander.SchemaNode(
404 repo_push_uri_change = colander.SchemaNode(
394 colander.String(),
405 colander.String(),
395 missing='NEW')
406 missing='NEW')
396
407
397 repo_push_uri = colander.SchemaNode(
408 repo_push_uri = colander.SchemaNode(
398 colander.String(),
409 colander.String(),
399 preparers=[preparers.strip_preparer],
410 preparers=[preparers.strip_preparer],
400 validator=deferred_sync_uri_validator,
411 validator=deferred_sync_uri_validator,
401 missing='')
412 missing='')
402
413
403 def deserialize(self, cstruct):
414 def deserialize(self, cstruct):
404 """
415 """
405 Custom deserialize that allows to chain validation, and verify
416 Custom deserialize that allows to chain validation, and verify
406 permissions, and as last step uniqueness
417 permissions, and as last step uniqueness
407 """
418 """
408
419
409 # first pass, to validate given data
420 # first pass, to validate given data
410 appstruct = super(RepoSchema, self).deserialize(cstruct)
421 appstruct = super(RepoSchema, self).deserialize(cstruct)
411 validated_name = appstruct['repo_name']
422 validated_name = appstruct['repo_name']
412 # because of repoSchema adds repo-group as an ID, we inject it as
423 # because of repoSchema adds repo-group as an ID, we inject it as
413 # full name here because validators require it, it's unwrapped later
424 # full name here because validators require it, it's unwrapped later
414 # so it's safe to use and final name is going to be without group anyway
425 # so it's safe to use and final name is going to be without group anyway
415
426
416 group, separator = get_repo_group(appstruct['repo_group'])
427 group, separator = get_repo_group(appstruct['repo_group'])
417 if group:
428 if group:
418 validated_name = separator.join([group.group_name, validated_name])
429 validated_name = separator.join([group.group_name, validated_name])
419
430
420 # second pass to validate permissions to repo_group
431 # second pass to validate permissions to repo_group
421 second = RepoGroupAccessSchema().bind(**self.bindings)
432 second = RepoGroupAccessSchema().bind(**self.bindings)
422 appstruct_second = second.deserialize({'repo_group': validated_name})
433 appstruct_second = second.deserialize({'repo_group': validated_name})
423 # save result
434 # save result
424 appstruct['repo_group'] = appstruct_second['repo_group']
435 appstruct['repo_group'] = appstruct_second['repo_group']
425
436
426 # thirds to validate uniqueness
437 # thirds to validate uniqueness
427 third = RepoNameUniqueSchema().bind(**self.bindings)
438 third = RepoNameUniqueSchema().bind(**self.bindings)
428 third.deserialize({'unique_repo_name': validated_name})
439 third.deserialize({'unique_repo_name': validated_name})
429
440
430 return appstruct
441 return appstruct
@@ -1,164 +1,151 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 ${h.secure_form(h.route_path('repo_create'), request=request)}
3 ${h.secure_form(h.route_path('repo_create'), request=request)}
4 <div class="form">
4 <div class="form">
5 <!-- fields -->
5 <!-- fields -->
6 <div class="fields">
6 <div class="fields">
7 <div class="field">
7 <div class="field">
8 <div class="label">
8 <div class="label">
9 <label for="repo_name">${_('Repository name')}:</label>
9 <label for="repo_name">${_('Repository name')}:</label>
10 </div>
10 </div>
11 <div class="input">
11 <div class="input">
12 ${h.text('repo_name', class_="medium")}
12 ${h.text('repo_name', class_="medium")}
13 <div class="info-block">
13 <div class="info-block">
14 <a id="remote_clone_toggle" href="#">${_('Import Existing Repository ?')}</a>
14 <a id="remote_clone_toggle" href="#">${_('Import Existing Repository ?')}</a>
15 </div>
15 </div>
16 %if not c.rhodecode_user.is_admin:
16 %if not c.rhodecode_user.is_admin:
17 ${h.hidden('user_created',True)}
17 ${h.hidden('user_created',True)}
18 %endif
18 %endif
19 </div>
19 </div>
20 </div>
20 </div>
21 <div id="remote_clone" class="field" style="display: none;">
21 <div id="remote_clone" class="field" style="display: none;">
22 <div class="label">
22 <div class="label">
23 <label for="clone_uri">${_('Clone from')}:</label>
23 <label for="clone_uri">${_('Clone from')}:</label>
24 </div>
24 </div>
25 <div class="input">
25 <div class="input">
26 ${h.text('clone_uri', class_="medium")}
26 ${h.text('clone_uri', class_="medium")}
27 <span class="help-block">
27 <span class="help-block">
28 <pre>
28 <pre>
29 - The repository must be accessible over http:// or https://
29 - The repository must be accessible over http:// or https://
30 - For Git projects it's recommended appending .git to the end of clone url.
30 - For Git projects it's recommended appending .git to the end of clone url.
31 - Make sure to select proper repository type from the below selector before importing it.
31 - Make sure to select proper repository type from the below selector before importing it.
32 - If your HTTP[S] repository is not publicly accessible,
32 - If your HTTP[S] repository is not publicly accessible,
33 add authentication information to the URL: https://username:password@server.company.com/repo-name.
33 add authentication information to the URL: https://username:password@server.company.com/repo-name.
34 - The Git LFS/Mercurial Largefiles objects will not be imported.
34 - The Git LFS/Mercurial Largefiles objects will not be imported.
35 - For very large repositories, it's recommended to manually copy them into the
35 - For very large repositories, it's recommended to manually copy them into the
36 RhodeCode <a href="${h.route_path('admin_settings_vcs', _anchor='vcs-storage-options')}">storage location</a> and run <a href="${h.route_path('admin_settings_mapping')}">Remap and Rescan</a>.
36 RhodeCode <a href="${h.route_path('admin_settings_vcs', _anchor='vcs-storage-options')}">storage location</a> and run <a href="${h.route_path('admin_settings_mapping')}">Remap and Rescan</a>.
37 </pre>
37 </pre>
38 </span>
38 </span>
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label">
42 <div class="label">
43 <label for="repo_type">${_('Type')}:</label>
44 </div>
45 <div class="select">
46 ${h.select('repo_type','hg',c.backends)}
47 <span class="help-block">${_('Set the type of repository to create.')}</span>
48 </div>
49 </div>
50 <div class="field">
51 <div class="label">
52 <label for="repo_group">${_('Repository group')}:</label>
43 <label for="repo_group">${_('Repository group')}:</label>
53 </div>
44 </div>
54 <div class="select">
45 <div class="select">
55 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
46 ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
56 % if c.personal_repo_group:
47 % if c.personal_repo_group:
57 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
48 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
58 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
49 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
59 </a>
50 </a>
60 % endif
51 % endif
61 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
52 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
62 </div>
53 </div>
63 </div>
54 </div>
64 <div class="field">
55 <div class="field">
65 <div class="label">
56 <div class="label">
57 <label for="repo_type">${_('Type')}:</label>
58 </div>
59 <div class="select">
60 ${h.select('repo_type','hg',c.backends)}
61 <span class="help-block">${_('Set the type of repository to create.')}</span>
62 </div>
63 </div>
64 <div class="field">
65 <div class="label">
66 <label for="repo_description">${_('Description')}:</label>
66 <label for="repo_description">${_('Description')}:</label>
67 </div>
67 </div>
68 <div class="textarea editor">
68 <div class="textarea editor">
69 ${h.textarea('repo_description',cols=23,rows=5,class_="medium")}
69 ${h.textarea('repo_description',cols=23,rows=5,class_="medium")}
70 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
70 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
71 <span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
71 <span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
72 <span id="meta-tags-desc" style="display: none">
72 <span id="meta-tags-desc" style="display: none">
73 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
73 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
74 ${dt.metatags_help()}
74 ${dt.metatags_help()}
75 </span>
75 </span>
76 </div>
76 </div>
77 </div>
77 </div>
78 <div class="field">
79 <div class="label">
80 <label for="repo_landing_rev">${_('Landing commit')}:</label>
81 </div>
82 <div class="select">
83 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
84 <span class="help-block">${_('The default commit for file pages, downloads, full text search index, and README generation.')}</span>
85 </div>
86 </div>
87 <div id="copy_perms" class="field">
78 <div id="copy_perms" class="field">
88 <div class="label label-checkbox">
79 <div class="label label-checkbox">
89 <label for="repo_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
80 <label for="repo_copy_permissions">${_('Copy Parent Group Permissions')}:</label>
90 </div>
81 </div>
91 <div class="checkboxes">
82 <div class="checkboxes">
92 ${h.checkbox('repo_copy_permissions', value="True", checked="checked")}
83 ${h.checkbox('repo_copy_permissions', value="True", checked="checked")}
93 <span class="help-block">${_('Copy permissions from parent repository group.')}</span>
84 <span class="help-block">${_('Copy permissions from parent repository group.')}</span>
94 </div>
85 </div>
95 </div>
86 </div>
96 <div class="field">
87 <div class="field">
97 <div class="label label-checkbox">
88 <div class="label label-checkbox">
98 <label for="repo_private">${_('Private Repository')}:</label>
89 <label for="repo_private">${_('Private Repository')}:</label>
99 </div>
90 </div>
100 <div class="checkboxes">
91 <div class="checkboxes">
101 ${h.checkbox('repo_private',value="True")}
92 ${h.checkbox('repo_private',value="True")}
102 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
93 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
103 </div>
94 </div>
104 </div>
95 </div>
105 <div class="buttons">
96 <div class="buttons">
106 ${h.submit('save',_('Save'),class_="btn")}
97 ${h.submit('save',_('Save'),class_="btn")}
107 </div>
98 </div>
108 </div>
99 </div>
109 </div>
100 </div>
110 <script>
101 <script>
111 $(document).ready(function(){
102 $(document).ready(function(){
112 var setCopyPermsOption = function(group_val){
103 var setCopyPermsOption = function(group_val){
113 if(group_val != "-1"){
104 if(group_val != "-1"){
114 $('#copy_perms').show()
105 $('#copy_perms').show()
115 }
106 }
116 else{
107 else{
117 $('#copy_perms').hide();
108 $('#copy_perms').hide();
118 }
109 }
119 };
110 };
120
111
121 $('#remote_clone_toggle').on('click', function(e){
112 $('#remote_clone_toggle').on('click', function(e){
122 $('#remote_clone').show();
113 $('#remote_clone').show();
123 e.preventDefault();
114 e.preventDefault();
124 });
115 });
125
116
126 if($('#remote_clone input').hasClass('error')){
117 if($('#remote_clone input').hasClass('error')){
127 $('#remote_clone').show();
118 $('#remote_clone').show();
128 }
119 }
129 if($('#remote_clone input').val()){
120 if($('#remote_clone input').val()){
130 $('#remote_clone').show();
121 $('#remote_clone').show();
131 }
122 }
132
123
133 $("#repo_group").select2({
124 $("#repo_group").select2({
134 'containerCssClass': "drop-menu",
125 'containerCssClass': "drop-menu",
135 'dropdownCssClass': "drop-menu-dropdown",
126 'dropdownCssClass': "drop-menu-dropdown",
136 'dropdownAutoWidth': true,
127 'dropdownAutoWidth': true,
137 'width': "resolve"
128 'width': "resolve"
138 });
129 });
139
130
140 setCopyPermsOption($('#repo_group').val());
131 setCopyPermsOption($('#repo_group').val());
141 $("#repo_group").on("change", function(e) {
132 $("#repo_group").on("change", function(e) {
142 setCopyPermsOption(e.val)
133 setCopyPermsOption(e.val)
143 });
134 });
144
135
145 $("#repo_type").select2({
136 $("#repo_type").select2({
146 'containerCssClass': "drop-menu",
137 'containerCssClass': "drop-menu",
147 'dropdownCssClass': "drop-menu-dropdown",
138 'dropdownCssClass': "drop-menu-dropdown",
148 'minimumResultsForSearch': -1,
139 'minimumResultsForSearch': -1,
149 });
140 });
150 $("#repo_landing_rev").select2({
141
151 'containerCssClass': "drop-menu",
152 'dropdownCssClass': "drop-menu-dropdown",
153 'minimumResultsForSearch': -1,
154 });
155 $('#repo_name').focus();
142 $('#repo_name').focus();
156
143
157 $('#select_my_group').on('click', function(e){
144 $('#select_my_group').on('click', function(e){
158 e.preventDefault();
145 e.preventDefault();
159 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
146 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
160 })
147 })
161
148
162 })
149 })
163 </script>
150 </script>
164 ${h.end_form()}
151 ${h.end_form()}
@@ -1,127 +1,117 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Fork repository %s') % c.repo_name}
5 ${_('Fork repository %s') % c.repo_name}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()"></%def>
11 <%def name="breadcrumbs_links()"></%def>
12
12
13 <%def name="menu_bar_nav()">
13 <%def name="menu_bar_nav()">
14 ${self.menu_items(active='repositories')}
14 ${self.menu_items(active='repositories')}
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_subnav()">
17 <%def name="menu_bar_subnav()">
18 ${self.repo_menu(active='options')}
18 ${self.repo_menu(active='options')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 ${h.secure_form(h.route_path('repo_fork_create',repo_name=c.rhodecode_db_repo.repo_name), request=request)}
23 ${h.secure_form(h.route_path('repo_fork_create',repo_name=c.rhodecode_db_repo.repo_name), request=request)}
24 <div class="form">
24 <div class="form">
25 <!-- fields -->
25 <!-- fields -->
26 <div class="fields">
26 <div class="fields">
27
27
28 <div class="field">
28 <div class="field">
29 <div class="label">
29 <div class="label">
30 <label for="repo_name">${_('Fork name')}:</label>
30 <label for="repo_name">${_('Fork name')}:</label>
31 </div>
31 </div>
32 <div class="input">
32 <div class="input">
33 ${h.text('repo_name', class_="medium")}
33 ${h.text('repo_name', class_="medium")}
34 ${h.hidden('repo_type',c.rhodecode_db_repo.repo_type)}
34 ${h.hidden('repo_type',c.rhodecode_db_repo.repo_type)}
35 ${h.hidden('fork_parent_id',c.rhodecode_db_repo.repo_id)}
35 ${h.hidden('fork_parent_id',c.rhodecode_db_repo.repo_id)}
36 </div>
36 </div>
37 </div>
37 </div>
38
38
39 <div class="field">
39 <div class="field">
40 <div class="label">
40 <div class="label">
41 <label for="repo_group">${_('Repository group')}:</label>
41 <label for="repo_group">${_('Repository group')}:</label>
42 </div>
42 </div>
43 <div class="select">
43 <div class="select">
44 ${h.select('repo_group','',c.repo_groups,class_="medium")}
44 ${h.select('repo_group','',c.repo_groups,class_="medium")}
45 % if c.personal_repo_group:
45 % if c.personal_repo_group:
46 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
46 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
47 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
47 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
48 </a>
48 </a>
49 % endif
49 % endif
50 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
50 <span class="help-block">${_('Optionally select a group to put this repository into.')}</span>
51 </div>
51 </div>
52 </div>
52 </div>
53
53
54 <div class="field">
54 <div class="field">
55 <div class="label label-textarea">
55 <div class="label label-textarea">
56 <label for="description">${_('Description')}:</label>
56 <label for="description">${_('Description')}:</label>
57 </div>
57 </div>
58 <div class="textarea editor">
58 <div class="textarea editor">
59 ${h.textarea('description',cols=23,rows=5,class_="medium")}
59 ${h.textarea('description',cols=23,rows=5,class_="medium")}
60 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
60 <% metatags_url = h.literal('''<a href="#metatagsShow" onclick="$('#meta-tags-desc').toggle();return false">meta-tags</a>''') %>
61 <span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
61 <span class="help-block">${_('Plain text format with support of {metatags}. Add a README file for longer descriptions').format(metatags=metatags_url)|n}</span>
62 <span id="meta-tags-desc" style="display: none">
62 <span id="meta-tags-desc" style="display: none">
63 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
63 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
64 ${dt.metatags_help()}
64 ${dt.metatags_help()}
65 </span>
65 </span>
66 </div>
66 </div>
67 </div>
67 </div>
68
68
69 <div class="field">
69 <div class="field">
70 <div class="label">
71 <label for="landing_rev">${_('Landing commit')}:</label>
72 </div>
73 <div class="select">
74 ${h.select('landing_rev','',c.landing_revs,class_="medium")}
75 <span class="help-block">${_('The default commit for file pages, downloads, full text search index, and README generation.')}</span>
76 </div>
77 </div>
78
79 <div class="field">
80 <div class="label label-checkbox">
70 <div class="label label-checkbox">
81 <label for="private">${_('Copy permissions')}:</label>
71 <label for="private">${_('Copy permissions')}:</label>
82 </div>
72 </div>
83 <div class="checkboxes">
73 <div class="checkboxes">
84 ${h.checkbox('copy_permissions',value="True", checked="checked")}
74 ${h.checkbox('copy_permissions',value="True", checked="checked")}
85 <span class="help-block">${_('Copy permissions from parent repository.')}</span>
75 <span class="help-block">${_('Copy permissions from parent repository.')}</span>
86 </div>
76 </div>
87 </div>
77 </div>
88
78
89 <div class="field">
79 <div class="field">
90 <div class="label label-checkbox">
80 <div class="label label-checkbox">
91 <label for="private">${_('Private')}:</label>
81 <label for="private">${_('Private')}:</label>
92 </div>
82 </div>
93 <div class="checkboxes">
83 <div class="checkboxes">
94 ${h.checkbox('private',value="True")}
84 ${h.checkbox('private',value="True")}
95 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
85 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
96 </div>
86 </div>
97 </div>
87 </div>
98
88
99 <div class="buttons">
89 <div class="buttons">
100 ${h.submit('',_('Fork this Repository'),class_="btn")}
90 ${h.submit('',_('Fork this Repository'),class_="btn")}
101 </div>
91 </div>
102 </div>
92 </div>
103 </div>
93 </div>
104 ${h.end_form()}
94 ${h.end_form()}
105 </div>
95 </div>
106 <script>
96 <script>
107 $(document).ready(function(){
97 $(document).ready(function(){
108 $("#repo_group").select2({
98 $("#repo_group").select2({
109 'dropdownAutoWidth': true,
99 'dropdownAutoWidth': true,
110 'containerCssClass': "drop-menu",
100 'containerCssClass': "drop-menu",
111 'dropdownCssClass': "drop-menu-dropdown",
101 'dropdownCssClass': "drop-menu-dropdown",
112 'width': "resolve"
102 'width': "resolve"
113 });
103 });
114 $("#landing_rev").select2({
104 $("#landing_rev").select2({
115 'containerCssClass': "drop-menu",
105 'containerCssClass': "drop-menu",
116 'dropdownCssClass': "drop-menu-dropdown",
106 'dropdownCssClass': "drop-menu-dropdown",
117 'minimumResultsForSearch': -1
107 'minimumResultsForSearch': -1
118 });
108 });
119 $('#repo_name').focus();
109 $('#repo_name').focus();
120
110
121 $('#select_my_group').on('click', function(e){
111 $('#select_my_group').on('click', function(e){
122 e.preventDefault();
112 e.preventDefault();
123 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
113 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
124 })
114 })
125 })
115 })
126 </script>
116 </script>
127 </%def>
117 </%def>
General Comments 0
You need to be logged in to leave comments. Login now