##// END OF EJS Templates
api: get_repos allows now to filter by root locations, and optionally traverse the returned data....
marcink -
r1267:b965be9c default
parent child Browse files
Show More
@@ -1,58 +1,129 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, jsonify)
26 from rhodecode.model.db import User
27 from rhodecode.model.db import User
27
28
28
29
29 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGetRepos(object):
31 class TestGetRepos(object):
31 def test_api_get_repos(self):
32 def test_api_get_repos(self):
32 id_, params = build_data(self.apikey, 'get_repos')
33 id_, params = build_data(self.apikey, 'get_repos')
33 response = api_call(self.app, params)
34 response = api_call(self.app, params)
34
35
35 result = []
36 result = []
36 for repo in RepoModel().get_all():
37 for repo in RepoModel().get_all():
37 result.append(repo.get_api_data(include_secrets=True))
38 result.append(repo.get_api_data(include_secrets=True))
38 ret = jsonify(result)
39 ret = jsonify(result)
39
40
40 expected = ret
41 expected = ret
41 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
42
43
44 def test_api_get_repos_only_toplevel(self, user_util):
45 repo_group = user_util.create_repo_group(auto_cleanup=True)
46 user_util.create_repo(parent=repo_group)
47
48 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
49 response = api_call(self.app, params)
50
51 result = []
52 for repo in RepoModel().get_repos_for_root(root=None):
53 result.append(repo.get_api_data(include_secrets=True))
54 expected = jsonify(result)
55
56 assert_ok(id_, expected, given=response.body)
57
58 def test_api_get_repos_with_wrong_root(self):
59 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
60 response = api_call(self.app, params)
61
62 expected = 'Root repository group `abracadabra` does not exist'
63 assert_error(id_, expected, given=response.body)
64
65 def test_api_get_repos_with_root(self, user_util):
66 repo_group = user_util.create_repo_group(auto_cleanup=True)
67 repo_group_name = repo_group.group_name
68
69 user_util.create_repo(parent=repo_group)
70 user_util.create_repo(parent=repo_group)
71
72 # nested, should not show up
73 user_util._test_name = '{}/'.format(repo_group_name)
74 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
75 user_util.create_repo(parent=sub_repo_group)
76
77 id_, params = build_data(self.apikey, 'get_repos',
78 root=repo_group_name, traverse=0)
79 response = api_call(self.app, params)
80
81 result = []
82 for repo in RepoModel().get_repos_for_root(repo_group):
83 result.append(repo.get_api_data(include_secrets=True))
84
85 assert len(result) == 2
86 expected = jsonify(result)
87 assert_ok(id_, expected, given=response.body)
88
89 def test_api_get_repos_with_root_and_traverse(self, user_util):
90 repo_group = user_util.create_repo_group(auto_cleanup=True)
91 repo_group_name = repo_group.group_name
92
93 user_util.create_repo(parent=repo_group)
94 user_util.create_repo(parent=repo_group)
95
96 # nested, should not show up
97 user_util._test_name = '{}/'.format(repo_group_name)
98 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
99 user_util.create_repo(parent=sub_repo_group)
100
101 id_, params = build_data(self.apikey, 'get_repos',
102 root=repo_group_name, traverse=1)
103 response = api_call(self.app, params)
104
105 result = []
106 for repo in RepoModel().get_repos_for_root(
107 repo_group_name, traverse=True):
108 result.append(repo.get_api_data(include_secrets=True))
109
110 assert len(result) == 3
111 expected = jsonify(result)
112 assert_ok(id_, expected, given=response.body)
113
43 def test_api_get_repos_non_admin(self):
114 def test_api_get_repos_non_admin(self):
44 id_, params = build_data(self.apikey_regular, 'get_repos')
115 id_, params = build_data(self.apikey_regular, 'get_repos')
45 response = api_call(self.app, params)
116 response = api_call(self.app, params)
46
117
47 user = User.get_by_username(self.TEST_USER_LOGIN)
118 user = User.get_by_username(self.TEST_USER_LOGIN)
48 allowed_repos = user.AuthUser.permissions['repositories']
119 allowed_repos = user.AuthUser.permissions['repositories']
49
120
50 result = []
121 result = []
51 for repo in RepoModel().get_all():
122 for repo in RepoModel().get_all():
52 perm = allowed_repos[repo.repo_name]
123 perm = allowed_repos[repo.repo_name]
53 if perm in ['repository.read', 'repository.write', 'repository.admin']:
124 if perm in ['repository.read', 'repository.write', 'repository.admin']:
54 result.append(repo.get_api_data())
125 result.append(repo.get_api_data())
55 ret = jsonify(result)
126 ret = jsonify(result)
56
127
57 expected = ret
128 expected = ret
58 assert_ok(id_, expected, given=response.body)
129 assert_ok(id_, expected, given=response.body)
@@ -1,1932 +1,1960 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 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.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
33 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
33 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
34 from rhodecode.lib.utils2 import str2bool, time_to_datetime
34 from rhodecode.lib.utils2 import str2bool, time_to_datetime
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.model.changeset_status import ChangesetStatusModel
36 from rhodecode.model.changeset_status import ChangesetStatusModel
37 from rhodecode.model.comment import ChangesetCommentsModel
37 from rhodecode.model.comment import ChangesetCommentsModel
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (
39 Session, ChangesetStatus, RepositoryField, Repository)
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup)
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.scm import ScmModel, RepoList
41 from rhodecode.model.scm import ScmModel, RepoList
42 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
42 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
43 from rhodecode.model import validation_schema
43 from rhodecode.model import validation_schema
44 from rhodecode.model.validation_schema.schemas import repo_schema
44 from rhodecode.model.validation_schema.schemas import repo_schema
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 @jsonrpc_method()
49 @jsonrpc_method()
50 def get_repo(request, apiuser, repoid, cache=Optional(True)):
50 def get_repo(request, apiuser, repoid, cache=Optional(True)):
51 """
51 """
52 Gets an existing repository by its name or repository_id.
52 Gets an existing repository by its name or repository_id.
53
53
54 The members section so the output returns users groups or users
54 The members section so the output returns users groups or users
55 associated with that repository.
55 associated with that repository.
56
56
57 This command can only be run using an |authtoken| with admin rights,
57 This command can only be run using an |authtoken| with admin rights,
58 or users with at least read rights to the |repo|.
58 or users with at least read rights to the |repo|.
59
59
60 :param apiuser: This is filled automatically from the |authtoken|.
60 :param apiuser: This is filled automatically from the |authtoken|.
61 :type apiuser: AuthUser
61 :type apiuser: AuthUser
62 :param repoid: The repository name or repository id.
62 :param repoid: The repository name or repository id.
63 :type repoid: str or int
63 :type repoid: str or int
64 :param cache: use the cached value for last changeset
64 :param cache: use the cached value for last changeset
65 :type: cache: Optional(bool)
65 :type: cache: Optional(bool)
66
66
67 Example output:
67 Example output:
68
68
69 .. code-block:: bash
69 .. code-block:: bash
70
70
71 {
71 {
72 "error": null,
72 "error": null,
73 "id": <repo_id>,
73 "id": <repo_id>,
74 "result": {
74 "result": {
75 "clone_uri": null,
75 "clone_uri": null,
76 "created_on": "timestamp",
76 "created_on": "timestamp",
77 "description": "repo description",
77 "description": "repo description",
78 "enable_downloads": false,
78 "enable_downloads": false,
79 "enable_locking": false,
79 "enable_locking": false,
80 "enable_statistics": false,
80 "enable_statistics": false,
81 "followers": [
81 "followers": [
82 {
82 {
83 "active": true,
83 "active": true,
84 "admin": false,
84 "admin": false,
85 "api_key": "****************************************",
85 "api_key": "****************************************",
86 "api_keys": [
86 "api_keys": [
87 "****************************************"
87 "****************************************"
88 ],
88 ],
89 "email": "user@example.com",
89 "email": "user@example.com",
90 "emails": [
90 "emails": [
91 "user@example.com"
91 "user@example.com"
92 ],
92 ],
93 "extern_name": "rhodecode",
93 "extern_name": "rhodecode",
94 "extern_type": "rhodecode",
94 "extern_type": "rhodecode",
95 "firstname": "username",
95 "firstname": "username",
96 "ip_addresses": [],
96 "ip_addresses": [],
97 "language": null,
97 "language": null,
98 "last_login": "2015-09-16T17:16:35.854",
98 "last_login": "2015-09-16T17:16:35.854",
99 "lastname": "surname",
99 "lastname": "surname",
100 "user_id": <user_id>,
100 "user_id": <user_id>,
101 "username": "name"
101 "username": "name"
102 }
102 }
103 ],
103 ],
104 "fork_of": "parent-repo",
104 "fork_of": "parent-repo",
105 "landing_rev": [
105 "landing_rev": [
106 "rev",
106 "rev",
107 "tip"
107 "tip"
108 ],
108 ],
109 "last_changeset": {
109 "last_changeset": {
110 "author": "User <user@example.com>",
110 "author": "User <user@example.com>",
111 "branch": "default",
111 "branch": "default",
112 "date": "timestamp",
112 "date": "timestamp",
113 "message": "last commit message",
113 "message": "last commit message",
114 "parents": [
114 "parents": [
115 {
115 {
116 "raw_id": "commit-id"
116 "raw_id": "commit-id"
117 }
117 }
118 ],
118 ],
119 "raw_id": "commit-id",
119 "raw_id": "commit-id",
120 "revision": <revision number>,
120 "revision": <revision number>,
121 "short_id": "short id"
121 "short_id": "short id"
122 },
122 },
123 "lock_reason": null,
123 "lock_reason": null,
124 "locked_by": null,
124 "locked_by": null,
125 "locked_date": null,
125 "locked_date": null,
126 "members": [
126 "members": [
127 {
127 {
128 "name": "super-admin-name",
128 "name": "super-admin-name",
129 "origin": "super-admin",
129 "origin": "super-admin",
130 "permission": "repository.admin",
130 "permission": "repository.admin",
131 "type": "user"
131 "type": "user"
132 },
132 },
133 {
133 {
134 "name": "owner-name",
134 "name": "owner-name",
135 "origin": "owner",
135 "origin": "owner",
136 "permission": "repository.admin",
136 "permission": "repository.admin",
137 "type": "user"
137 "type": "user"
138 },
138 },
139 {
139 {
140 "name": "user-group-name",
140 "name": "user-group-name",
141 "origin": "permission",
141 "origin": "permission",
142 "permission": "repository.write",
142 "permission": "repository.write",
143 "type": "user_group"
143 "type": "user_group"
144 }
144 }
145 ],
145 ],
146 "owner": "owner-name",
146 "owner": "owner-name",
147 "permissions": [
147 "permissions": [
148 {
148 {
149 "name": "super-admin-name",
149 "name": "super-admin-name",
150 "origin": "super-admin",
150 "origin": "super-admin",
151 "permission": "repository.admin",
151 "permission": "repository.admin",
152 "type": "user"
152 "type": "user"
153 },
153 },
154 {
154 {
155 "name": "owner-name",
155 "name": "owner-name",
156 "origin": "owner",
156 "origin": "owner",
157 "permission": "repository.admin",
157 "permission": "repository.admin",
158 "type": "user"
158 "type": "user"
159 },
159 },
160 {
160 {
161 "name": "user-group-name",
161 "name": "user-group-name",
162 "origin": "permission",
162 "origin": "permission",
163 "permission": "repository.write",
163 "permission": "repository.write",
164 "type": "user_group"
164 "type": "user_group"
165 }
165 }
166 ],
166 ],
167 "private": true,
167 "private": true,
168 "repo_id": 676,
168 "repo_id": 676,
169 "repo_name": "user-group/repo-name",
169 "repo_name": "user-group/repo-name",
170 "repo_type": "hg"
170 "repo_type": "hg"
171 }
171 }
172 }
172 }
173 """
173 """
174
174
175 repo = get_repo_or_error(repoid)
175 repo = get_repo_or_error(repoid)
176 cache = Optional.extract(cache)
176 cache = Optional.extract(cache)
177
177
178 include_secrets = False
178 include_secrets = False
179 if has_superadmin_permission(apiuser):
179 if has_superadmin_permission(apiuser):
180 include_secrets = True
180 include_secrets = True
181 else:
181 else:
182 # check if we have at least read permission for this repo !
182 # check if we have at least read permission for this repo !
183 _perms = (
183 _perms = (
184 'repository.admin', 'repository.write', 'repository.read',)
184 'repository.admin', 'repository.write', 'repository.read',)
185 validate_repo_permissions(apiuser, repoid, repo, _perms)
185 validate_repo_permissions(apiuser, repoid, repo, _perms)
186
186
187 permissions = []
187 permissions = []
188 for _user in repo.permissions():
188 for _user in repo.permissions():
189 user_data = {
189 user_data = {
190 'name': _user.username,
190 'name': _user.username,
191 'permission': _user.permission,
191 'permission': _user.permission,
192 'origin': get_origin(_user),
192 'origin': get_origin(_user),
193 'type': "user",
193 'type': "user",
194 }
194 }
195 permissions.append(user_data)
195 permissions.append(user_data)
196
196
197 for _user_group in repo.permission_user_groups():
197 for _user_group in repo.permission_user_groups():
198 user_group_data = {
198 user_group_data = {
199 'name': _user_group.users_group_name,
199 'name': _user_group.users_group_name,
200 'permission': _user_group.permission,
200 'permission': _user_group.permission,
201 'origin': get_origin(_user_group),
201 'origin': get_origin(_user_group),
202 'type': "user_group",
202 'type': "user_group",
203 }
203 }
204 permissions.append(user_group_data)
204 permissions.append(user_group_data)
205
205
206 following_users = [
206 following_users = [
207 user.user.get_api_data(include_secrets=include_secrets)
207 user.user.get_api_data(include_secrets=include_secrets)
208 for user in repo.followers]
208 for user in repo.followers]
209
209
210 if not cache:
210 if not cache:
211 repo.update_commit_cache()
211 repo.update_commit_cache()
212 data = repo.get_api_data(include_secrets=include_secrets)
212 data = repo.get_api_data(include_secrets=include_secrets)
213 data['members'] = permissions # TODO: this should be deprecated soon
213 data['members'] = permissions # TODO: this should be deprecated soon
214 data['permissions'] = permissions
214 data['permissions'] = permissions
215 data['followers'] = following_users
215 data['followers'] = following_users
216 return data
216 return data
217
217
218
218
219 @jsonrpc_method()
219 @jsonrpc_method()
220 def get_repos(request, apiuser):
220 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
221 """
221 """
222 Lists all existing repositories.
222 Lists all existing repositories.
223
223
224 This command can only be run using an |authtoken| with admin rights,
224 This command can only be run using an |authtoken| with admin rights,
225 or users with at least read rights to |repos|.
225 or users with at least read rights to |repos|.
226
226
227 :param apiuser: This is filled automatically from the |authtoken|.
227 :param apiuser: This is filled automatically from the |authtoken|.
228 :type apiuser: AuthUser
228 :type apiuser: AuthUser
229 :param root: specify root repository group to fetch repositories.
230 filters the returned repositories to be members of given root group.
231 :type root: Optional(None)
232 :param traverse: traverse given root into subrepositories. With this flag
233 set to False, it will only return top-level repositories from `root`.
234 if root is empty it will return just top-level repositories.
235 :type traverse: Optional(True)
236
229
237
230 Example output:
238 Example output:
231
239
232 .. code-block:: bash
240 .. code-block:: bash
233
241
234 id : <id_given_in_input>
242 id : <id_given_in_input>
235 result: [
243 result: [
236 {
244 {
237 "repo_id" : "<repo_id>",
245 "repo_id" : "<repo_id>",
238 "repo_name" : "<reponame>"
246 "repo_name" : "<reponame>"
239 "repo_type" : "<repo_type>",
247 "repo_type" : "<repo_type>",
240 "clone_uri" : "<clone_uri>",
248 "clone_uri" : "<clone_uri>",
241 "private": : "<bool>",
249 "private": : "<bool>",
242 "created_on" : "<datetimecreated>",
250 "created_on" : "<datetimecreated>",
243 "description" : "<description>",
251 "description" : "<description>",
244 "landing_rev": "<landing_rev>",
252 "landing_rev": "<landing_rev>",
245 "owner": "<repo_owner>",
253 "owner": "<repo_owner>",
246 "fork_of": "<name_of_fork_parent>",
254 "fork_of": "<name_of_fork_parent>",
247 "enable_downloads": "<bool>",
255 "enable_downloads": "<bool>",
248 "enable_locking": "<bool>",
256 "enable_locking": "<bool>",
249 "enable_statistics": "<bool>",
257 "enable_statistics": "<bool>",
250 },
258 },
251 ...
259 ...
252 ]
260 ]
253 error: null
261 error: null
254 """
262 """
255
263
256 include_secrets = has_superadmin_permission(apiuser)
264 include_secrets = has_superadmin_permission(apiuser)
257 _perms = ('repository.read', 'repository.write', 'repository.admin',)
265 _perms = ('repository.read', 'repository.write', 'repository.admin',)
258 extras = {'user': apiuser}
266 extras = {'user': apiuser}
259
267
260 repo_list = RepoList(
268 root = Optional.extract(root)
261 RepoModel().get_all(), perm_set=_perms, extra_kwargs=extras)
269 traverse = Optional.extract(traverse, binary=True)
270
271 if root:
272 # verify parent existance, if it's empty return an error
273 parent = RepoGroup.get_by_group_name(root)
274 if not parent:
275 raise JSONRPCError(
276 'Root repository group `{}` does not exist'.format(root))
277
278 if traverse:
279 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
280 else:
281 repos = RepoModel().get_repos_for_root(root=parent)
282 else:
283 if traverse:
284 repos = RepoModel().get_all()
285 else:
286 # return just top-level
287 repos = RepoModel().get_repos_for_root(root=None)
288
289 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
262 return [repo.get_api_data(include_secrets=include_secrets)
290 return [repo.get_api_data(include_secrets=include_secrets)
263 for repo in repo_list]
291 for repo in repo_list]
264
292
265
293
266 @jsonrpc_method()
294 @jsonrpc_method()
267 def get_repo_changeset(request, apiuser, repoid, revision,
295 def get_repo_changeset(request, apiuser, repoid, revision,
268 details=Optional('basic')):
296 details=Optional('basic')):
269 """
297 """
270 Returns information about a changeset.
298 Returns information about a changeset.
271
299
272 Additionally parameters define the amount of details returned by
300 Additionally parameters define the amount of details returned by
273 this function.
301 this function.
274
302
275 This command can only be run using an |authtoken| with admin rights,
303 This command can only be run using an |authtoken| with admin rights,
276 or users with at least read rights to the |repo|.
304 or users with at least read rights to the |repo|.
277
305
278 :param apiuser: This is filled automatically from the |authtoken|.
306 :param apiuser: This is filled automatically from the |authtoken|.
279 :type apiuser: AuthUser
307 :type apiuser: AuthUser
280 :param repoid: The repository name or repository id
308 :param repoid: The repository name or repository id
281 :type repoid: str or int
309 :type repoid: str or int
282 :param revision: revision for which listing should be done
310 :param revision: revision for which listing should be done
283 :type revision: str
311 :type revision: str
284 :param details: details can be 'basic|extended|full' full gives diff
312 :param details: details can be 'basic|extended|full' full gives diff
285 info details like the diff itself, and number of changed files etc.
313 info details like the diff itself, and number of changed files etc.
286 :type details: Optional(str)
314 :type details: Optional(str)
287
315
288 """
316 """
289 repo = get_repo_or_error(repoid)
317 repo = get_repo_or_error(repoid)
290 if not has_superadmin_permission(apiuser):
318 if not has_superadmin_permission(apiuser):
291 _perms = (
319 _perms = (
292 'repository.admin', 'repository.write', 'repository.read',)
320 'repository.admin', 'repository.write', 'repository.read',)
293 validate_repo_permissions(apiuser, repoid, repo, _perms)
321 validate_repo_permissions(apiuser, repoid, repo, _perms)
294
322
295 changes_details = Optional.extract(details)
323 changes_details = Optional.extract(details)
296 _changes_details_types = ['basic', 'extended', 'full']
324 _changes_details_types = ['basic', 'extended', 'full']
297 if changes_details not in _changes_details_types:
325 if changes_details not in _changes_details_types:
298 raise JSONRPCError(
326 raise JSONRPCError(
299 'ret_type must be one of %s' % (
327 'ret_type must be one of %s' % (
300 ','.join(_changes_details_types)))
328 ','.join(_changes_details_types)))
301
329
302 pre_load = ['author', 'branch', 'date', 'message', 'parents',
330 pre_load = ['author', 'branch', 'date', 'message', 'parents',
303 'status', '_commit', '_file_paths']
331 'status', '_commit', '_file_paths']
304
332
305 try:
333 try:
306 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
334 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
307 except TypeError as e:
335 except TypeError as e:
308 raise JSONRPCError(e.message)
336 raise JSONRPCError(e.message)
309 _cs_json = cs.__json__()
337 _cs_json = cs.__json__()
310 _cs_json['diff'] = build_commit_data(cs, changes_details)
338 _cs_json['diff'] = build_commit_data(cs, changes_details)
311 if changes_details == 'full':
339 if changes_details == 'full':
312 _cs_json['refs'] = {
340 _cs_json['refs'] = {
313 'branches': [cs.branch],
341 'branches': [cs.branch],
314 'bookmarks': getattr(cs, 'bookmarks', []),
342 'bookmarks': getattr(cs, 'bookmarks', []),
315 'tags': cs.tags
343 'tags': cs.tags
316 }
344 }
317 return _cs_json
345 return _cs_json
318
346
319
347
320 @jsonrpc_method()
348 @jsonrpc_method()
321 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
349 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
322 details=Optional('basic')):
350 details=Optional('basic')):
323 """
351 """
324 Returns a set of commits limited by the number starting
352 Returns a set of commits limited by the number starting
325 from the `start_rev` option.
353 from the `start_rev` option.
326
354
327 Additional parameters define the amount of details returned by this
355 Additional parameters define the amount of details returned by this
328 function.
356 function.
329
357
330 This command can only be run using an |authtoken| with admin rights,
358 This command can only be run using an |authtoken| with admin rights,
331 or users with at least read rights to |repos|.
359 or users with at least read rights to |repos|.
332
360
333 :param apiuser: This is filled automatically from the |authtoken|.
361 :param apiuser: This is filled automatically from the |authtoken|.
334 :type apiuser: AuthUser
362 :type apiuser: AuthUser
335 :param repoid: The repository name or repository ID.
363 :param repoid: The repository name or repository ID.
336 :type repoid: str or int
364 :type repoid: str or int
337 :param start_rev: The starting revision from where to get changesets.
365 :param start_rev: The starting revision from where to get changesets.
338 :type start_rev: str
366 :type start_rev: str
339 :param limit: Limit the number of commits to this amount
367 :param limit: Limit the number of commits to this amount
340 :type limit: str or int
368 :type limit: str or int
341 :param details: Set the level of detail returned. Valid option are:
369 :param details: Set the level of detail returned. Valid option are:
342 ``basic``, ``extended`` and ``full``.
370 ``basic``, ``extended`` and ``full``.
343 :type details: Optional(str)
371 :type details: Optional(str)
344
372
345 .. note::
373 .. note::
346
374
347 Setting the parameter `details` to the value ``full`` is extensive
375 Setting the parameter `details` to the value ``full`` is extensive
348 and returns details like the diff itself, and the number
376 and returns details like the diff itself, and the number
349 of changed files.
377 of changed files.
350
378
351 """
379 """
352 repo = get_repo_or_error(repoid)
380 repo = get_repo_or_error(repoid)
353 if not has_superadmin_permission(apiuser):
381 if not has_superadmin_permission(apiuser):
354 _perms = (
382 _perms = (
355 'repository.admin', 'repository.write', 'repository.read',)
383 'repository.admin', 'repository.write', 'repository.read',)
356 validate_repo_permissions(apiuser, repoid, repo, _perms)
384 validate_repo_permissions(apiuser, repoid, repo, _perms)
357
385
358 changes_details = Optional.extract(details)
386 changes_details = Optional.extract(details)
359 _changes_details_types = ['basic', 'extended', 'full']
387 _changes_details_types = ['basic', 'extended', 'full']
360 if changes_details not in _changes_details_types:
388 if changes_details not in _changes_details_types:
361 raise JSONRPCError(
389 raise JSONRPCError(
362 'ret_type must be one of %s' % (
390 'ret_type must be one of %s' % (
363 ','.join(_changes_details_types)))
391 ','.join(_changes_details_types)))
364
392
365 limit = int(limit)
393 limit = int(limit)
366 pre_load = ['author', 'branch', 'date', 'message', 'parents',
394 pre_load = ['author', 'branch', 'date', 'message', 'parents',
367 'status', '_commit', '_file_paths']
395 'status', '_commit', '_file_paths']
368
396
369 vcs_repo = repo.scm_instance()
397 vcs_repo = repo.scm_instance()
370 # SVN needs a special case to distinguish its index and commit id
398 # SVN needs a special case to distinguish its index and commit id
371 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
399 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
372 start_rev = vcs_repo.commit_ids[0]
400 start_rev = vcs_repo.commit_ids[0]
373
401
374 try:
402 try:
375 commits = vcs_repo.get_commits(
403 commits = vcs_repo.get_commits(
376 start_id=start_rev, pre_load=pre_load)
404 start_id=start_rev, pre_load=pre_load)
377 except TypeError as e:
405 except TypeError as e:
378 raise JSONRPCError(e.message)
406 raise JSONRPCError(e.message)
379 except Exception:
407 except Exception:
380 log.exception('Fetching of commits failed')
408 log.exception('Fetching of commits failed')
381 raise JSONRPCError('Error occurred during commit fetching')
409 raise JSONRPCError('Error occurred during commit fetching')
382
410
383 ret = []
411 ret = []
384 for cnt, commit in enumerate(commits):
412 for cnt, commit in enumerate(commits):
385 if cnt >= limit != -1:
413 if cnt >= limit != -1:
386 break
414 break
387 _cs_json = commit.__json__()
415 _cs_json = commit.__json__()
388 _cs_json['diff'] = build_commit_data(commit, changes_details)
416 _cs_json['diff'] = build_commit_data(commit, changes_details)
389 if changes_details == 'full':
417 if changes_details == 'full':
390 _cs_json['refs'] = {
418 _cs_json['refs'] = {
391 'branches': [commit.branch],
419 'branches': [commit.branch],
392 'bookmarks': getattr(commit, 'bookmarks', []),
420 'bookmarks': getattr(commit, 'bookmarks', []),
393 'tags': commit.tags
421 'tags': commit.tags
394 }
422 }
395 ret.append(_cs_json)
423 ret.append(_cs_json)
396 return ret
424 return ret
397
425
398
426
399 @jsonrpc_method()
427 @jsonrpc_method()
400 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
428 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
401 ret_type=Optional('all'), details=Optional('basic'),
429 ret_type=Optional('all'), details=Optional('basic'),
402 max_file_bytes=Optional(None)):
430 max_file_bytes=Optional(None)):
403 """
431 """
404 Returns a list of nodes and children in a flat list for a given
432 Returns a list of nodes and children in a flat list for a given
405 path at given revision.
433 path at given revision.
406
434
407 It's possible to specify ret_type to show only `files` or `dirs`.
435 It's possible to specify ret_type to show only `files` or `dirs`.
408
436
409 This command can only be run using an |authtoken| with admin rights,
437 This command can only be run using an |authtoken| with admin rights,
410 or users with at least read rights to |repos|.
438 or users with at least read rights to |repos|.
411
439
412 :param apiuser: This is filled automatically from the |authtoken|.
440 :param apiuser: This is filled automatically from the |authtoken|.
413 :type apiuser: AuthUser
441 :type apiuser: AuthUser
414 :param repoid: The repository name or repository ID.
442 :param repoid: The repository name or repository ID.
415 :type repoid: str or int
443 :type repoid: str or int
416 :param revision: The revision for which listing should be done.
444 :param revision: The revision for which listing should be done.
417 :type revision: str
445 :type revision: str
418 :param root_path: The path from which to start displaying.
446 :param root_path: The path from which to start displaying.
419 :type root_path: str
447 :type root_path: str
420 :param ret_type: Set the return type. Valid options are
448 :param ret_type: Set the return type. Valid options are
421 ``all`` (default), ``files`` and ``dirs``.
449 ``all`` (default), ``files`` and ``dirs``.
422 :type ret_type: Optional(str)
450 :type ret_type: Optional(str)
423 :param details: Returns extended information about nodes, such as
451 :param details: Returns extended information about nodes, such as
424 md5, binary, and or content. The valid options are ``basic`` and
452 md5, binary, and or content. The valid options are ``basic`` and
425 ``full``.
453 ``full``.
426 :type details: Optional(str)
454 :type details: Optional(str)
427 :param max_file_bytes: Only return file content under this file size bytes
455 :param max_file_bytes: Only return file content under this file size bytes
428 :type details: Optional(int)
456 :type details: Optional(int)
429
457
430 Example output:
458 Example output:
431
459
432 .. code-block:: bash
460 .. code-block:: bash
433
461
434 id : <id_given_in_input>
462 id : <id_given_in_input>
435 result: [
463 result: [
436 {
464 {
437 "name" : "<name>"
465 "name" : "<name>"
438 "type" : "<type>",
466 "type" : "<type>",
439 "binary": "<true|false>" (only in extended mode)
467 "binary": "<true|false>" (only in extended mode)
440 "md5" : "<md5 of file content>" (only in extended mode)
468 "md5" : "<md5 of file content>" (only in extended mode)
441 },
469 },
442 ...
470 ...
443 ]
471 ]
444 error: null
472 error: null
445 """
473 """
446
474
447 repo = get_repo_or_error(repoid)
475 repo = get_repo_or_error(repoid)
448 if not has_superadmin_permission(apiuser):
476 if not has_superadmin_permission(apiuser):
449 _perms = (
477 _perms = (
450 'repository.admin', 'repository.write', 'repository.read',)
478 'repository.admin', 'repository.write', 'repository.read',)
451 validate_repo_permissions(apiuser, repoid, repo, _perms)
479 validate_repo_permissions(apiuser, repoid, repo, _perms)
452
480
453 ret_type = Optional.extract(ret_type)
481 ret_type = Optional.extract(ret_type)
454 details = Optional.extract(details)
482 details = Optional.extract(details)
455 _extended_types = ['basic', 'full']
483 _extended_types = ['basic', 'full']
456 if details not in _extended_types:
484 if details not in _extended_types:
457 raise JSONRPCError(
485 raise JSONRPCError(
458 'ret_type must be one of %s' % (','.join(_extended_types)))
486 'ret_type must be one of %s' % (','.join(_extended_types)))
459 extended_info = False
487 extended_info = False
460 content = False
488 content = False
461 if details == 'basic':
489 if details == 'basic':
462 extended_info = True
490 extended_info = True
463
491
464 if details == 'full':
492 if details == 'full':
465 extended_info = content = True
493 extended_info = content = True
466
494
467 _map = {}
495 _map = {}
468 try:
496 try:
469 # check if repo is not empty by any chance, skip quicker if it is.
497 # check if repo is not empty by any chance, skip quicker if it is.
470 _scm = repo.scm_instance()
498 _scm = repo.scm_instance()
471 if _scm.is_empty():
499 if _scm.is_empty():
472 return []
500 return []
473
501
474 _d, _f = ScmModel().get_nodes(
502 _d, _f = ScmModel().get_nodes(
475 repo, revision, root_path, flat=False,
503 repo, revision, root_path, flat=False,
476 extended_info=extended_info, content=content,
504 extended_info=extended_info, content=content,
477 max_file_bytes=max_file_bytes)
505 max_file_bytes=max_file_bytes)
478 _map = {
506 _map = {
479 'all': _d + _f,
507 'all': _d + _f,
480 'files': _f,
508 'files': _f,
481 'dirs': _d,
509 'dirs': _d,
482 }
510 }
483 return _map[ret_type]
511 return _map[ret_type]
484 except KeyError:
512 except KeyError:
485 raise JSONRPCError(
513 raise JSONRPCError(
486 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
514 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
487 except Exception:
515 except Exception:
488 log.exception("Exception occurred while trying to get repo nodes")
516 log.exception("Exception occurred while trying to get repo nodes")
489 raise JSONRPCError(
517 raise JSONRPCError(
490 'failed to get repo: `%s` nodes' % repo.repo_name
518 'failed to get repo: `%s` nodes' % repo.repo_name
491 )
519 )
492
520
493
521
494 @jsonrpc_method()
522 @jsonrpc_method()
495 def get_repo_refs(request, apiuser, repoid):
523 def get_repo_refs(request, apiuser, repoid):
496 """
524 """
497 Returns a dictionary of current references. It returns
525 Returns a dictionary of current references. It returns
498 bookmarks, branches, closed_branches, and tags for given repository
526 bookmarks, branches, closed_branches, and tags for given repository
499
527
500 It's possible to specify ret_type to show only `files` or `dirs`.
528 It's possible to specify ret_type to show only `files` or `dirs`.
501
529
502 This command can only be run using an |authtoken| with admin rights,
530 This command can only be run using an |authtoken| with admin rights,
503 or users with at least read rights to |repos|.
531 or users with at least read rights to |repos|.
504
532
505 :param apiuser: This is filled automatically from the |authtoken|.
533 :param apiuser: This is filled automatically from the |authtoken|.
506 :type apiuser: AuthUser
534 :type apiuser: AuthUser
507 :param repoid: The repository name or repository ID.
535 :param repoid: The repository name or repository ID.
508 :type repoid: str or int
536 :type repoid: str or int
509
537
510 Example output:
538 Example output:
511
539
512 .. code-block:: bash
540 .. code-block:: bash
513
541
514 id : <id_given_in_input>
542 id : <id_given_in_input>
515 "result": {
543 "result": {
516 "bookmarks": {
544 "bookmarks": {
517 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
545 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
518 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
546 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
519 },
547 },
520 "branches": {
548 "branches": {
521 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
549 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
522 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
550 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
523 },
551 },
524 "branches_closed": {},
552 "branches_closed": {},
525 "tags": {
553 "tags": {
526 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
554 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
527 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
555 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
528 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
556 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
529 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
557 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
530 }
558 }
531 }
559 }
532 error: null
560 error: null
533 """
561 """
534
562
535 repo = get_repo_or_error(repoid)
563 repo = get_repo_or_error(repoid)
536 if not has_superadmin_permission(apiuser):
564 if not has_superadmin_permission(apiuser):
537 _perms = ('repository.admin', 'repository.write', 'repository.read',)
565 _perms = ('repository.admin', 'repository.write', 'repository.read',)
538 validate_repo_permissions(apiuser, repoid, repo, _perms)
566 validate_repo_permissions(apiuser, repoid, repo, _perms)
539
567
540 try:
568 try:
541 # check if repo is not empty by any chance, skip quicker if it is.
569 # check if repo is not empty by any chance, skip quicker if it is.
542 vcs_instance = repo.scm_instance()
570 vcs_instance = repo.scm_instance()
543 refs = vcs_instance.refs()
571 refs = vcs_instance.refs()
544 return refs
572 return refs
545 except Exception:
573 except Exception:
546 log.exception("Exception occurred while trying to get repo refs")
574 log.exception("Exception occurred while trying to get repo refs")
547 raise JSONRPCError(
575 raise JSONRPCError(
548 'failed to get repo: `%s` references' % repo.repo_name
576 'failed to get repo: `%s` references' % repo.repo_name
549 )
577 )
550
578
551
579
552 @jsonrpc_method()
580 @jsonrpc_method()
553 def create_repo(
581 def create_repo(
554 request, apiuser, repo_name, repo_type,
582 request, apiuser, repo_name, repo_type,
555 owner=Optional(OAttr('apiuser')),
583 owner=Optional(OAttr('apiuser')),
556 description=Optional(''),
584 description=Optional(''),
557 private=Optional(False),
585 private=Optional(False),
558 clone_uri=Optional(None),
586 clone_uri=Optional(None),
559 landing_rev=Optional('rev:tip'),
587 landing_rev=Optional('rev:tip'),
560 enable_statistics=Optional(False),
588 enable_statistics=Optional(False),
561 enable_locking=Optional(False),
589 enable_locking=Optional(False),
562 enable_downloads=Optional(False),
590 enable_downloads=Optional(False),
563 copy_permissions=Optional(False)):
591 copy_permissions=Optional(False)):
564 """
592 """
565 Creates a repository.
593 Creates a repository.
566
594
567 * If the repository name contains "/", repository will be created inside
595 * If the repository name contains "/", repository will be created inside
568 a repository group or nested repository groups
596 a repository group or nested repository groups
569
597
570 For example "foo/bar/repo1" will create |repo| called "repo1" inside
598 For example "foo/bar/repo1" will create |repo| called "repo1" inside
571 group "foo/bar". You have to have permissions to access and write to
599 group "foo/bar". You have to have permissions to access and write to
572 the last repository group ("bar" in this example)
600 the last repository group ("bar" in this example)
573
601
574 This command can only be run using an |authtoken| with at least
602 This command can only be run using an |authtoken| with at least
575 permissions to create repositories, or write permissions to
603 permissions to create repositories, or write permissions to
576 parent repository groups.
604 parent repository groups.
577
605
578 :param apiuser: This is filled automatically from the |authtoken|.
606 :param apiuser: This is filled automatically from the |authtoken|.
579 :type apiuser: AuthUser
607 :type apiuser: AuthUser
580 :param repo_name: Set the repository name.
608 :param repo_name: Set the repository name.
581 :type repo_name: str
609 :type repo_name: str
582 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
610 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
583 :type repo_type: str
611 :type repo_type: str
584 :param owner: user_id or username
612 :param owner: user_id or username
585 :type owner: Optional(str)
613 :type owner: Optional(str)
586 :param description: Set the repository description.
614 :param description: Set the repository description.
587 :type description: Optional(str)
615 :type description: Optional(str)
588 :param private: set repository as private
616 :param private: set repository as private
589 :type private: bool
617 :type private: bool
590 :param clone_uri: set clone_uri
618 :param clone_uri: set clone_uri
591 :type clone_uri: str
619 :type clone_uri: str
592 :param landing_rev: <rev_type>:<rev>
620 :param landing_rev: <rev_type>:<rev>
593 :type landing_rev: str
621 :type landing_rev: str
594 :param enable_locking:
622 :param enable_locking:
595 :type enable_locking: bool
623 :type enable_locking: bool
596 :param enable_downloads:
624 :param enable_downloads:
597 :type enable_downloads: bool
625 :type enable_downloads: bool
598 :param enable_statistics:
626 :param enable_statistics:
599 :type enable_statistics: bool
627 :type enable_statistics: bool
600 :param copy_permissions: Copy permission from group in which the
628 :param copy_permissions: Copy permission from group in which the
601 repository is being created.
629 repository is being created.
602 :type copy_permissions: bool
630 :type copy_permissions: bool
603
631
604
632
605 Example output:
633 Example output:
606
634
607 .. code-block:: bash
635 .. code-block:: bash
608
636
609 id : <id_given_in_input>
637 id : <id_given_in_input>
610 result: {
638 result: {
611 "msg": "Created new repository `<reponame>`",
639 "msg": "Created new repository `<reponame>`",
612 "success": true,
640 "success": true,
613 "task": "<celery task id or None if done sync>"
641 "task": "<celery task id or None if done sync>"
614 }
642 }
615 error: null
643 error: null
616
644
617
645
618 Example error output:
646 Example error output:
619
647
620 .. code-block:: bash
648 .. code-block:: bash
621
649
622 id : <id_given_in_input>
650 id : <id_given_in_input>
623 result : null
651 result : null
624 error : {
652 error : {
625 'failed to create repository `<repo_name>`'
653 'failed to create repository `<repo_name>`'
626 }
654 }
627
655
628 """
656 """
629
657
630 owner = validate_set_owner_permissions(apiuser, owner)
658 owner = validate_set_owner_permissions(apiuser, owner)
631
659
632 description = Optional.extract(description)
660 description = Optional.extract(description)
633 copy_permissions = Optional.extract(copy_permissions)
661 copy_permissions = Optional.extract(copy_permissions)
634 clone_uri = Optional.extract(clone_uri)
662 clone_uri = Optional.extract(clone_uri)
635 landing_commit_ref = Optional.extract(landing_rev)
663 landing_commit_ref = Optional.extract(landing_rev)
636
664
637 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
665 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
638 if isinstance(private, Optional):
666 if isinstance(private, Optional):
639 private = defs.get('repo_private') or Optional.extract(private)
667 private = defs.get('repo_private') or Optional.extract(private)
640 if isinstance(repo_type, Optional):
668 if isinstance(repo_type, Optional):
641 repo_type = defs.get('repo_type')
669 repo_type = defs.get('repo_type')
642 if isinstance(enable_statistics, Optional):
670 if isinstance(enable_statistics, Optional):
643 enable_statistics = defs.get('repo_enable_statistics')
671 enable_statistics = defs.get('repo_enable_statistics')
644 if isinstance(enable_locking, Optional):
672 if isinstance(enable_locking, Optional):
645 enable_locking = defs.get('repo_enable_locking')
673 enable_locking = defs.get('repo_enable_locking')
646 if isinstance(enable_downloads, Optional):
674 if isinstance(enable_downloads, Optional):
647 enable_downloads = defs.get('repo_enable_downloads')
675 enable_downloads = defs.get('repo_enable_downloads')
648
676
649 schema = repo_schema.RepoSchema().bind(
677 schema = repo_schema.RepoSchema().bind(
650 repo_type_options=rhodecode.BACKENDS.keys(),
678 repo_type_options=rhodecode.BACKENDS.keys(),
651 # user caller
679 # user caller
652 user=apiuser)
680 user=apiuser)
653
681
654 try:
682 try:
655 schema_data = schema.deserialize(dict(
683 schema_data = schema.deserialize(dict(
656 repo_name=repo_name,
684 repo_name=repo_name,
657 repo_type=repo_type,
685 repo_type=repo_type,
658 repo_owner=owner.username,
686 repo_owner=owner.username,
659 repo_description=description,
687 repo_description=description,
660 repo_landing_commit_ref=landing_commit_ref,
688 repo_landing_commit_ref=landing_commit_ref,
661 repo_clone_uri=clone_uri,
689 repo_clone_uri=clone_uri,
662 repo_private=private,
690 repo_private=private,
663 repo_copy_permissions=copy_permissions,
691 repo_copy_permissions=copy_permissions,
664 repo_enable_statistics=enable_statistics,
692 repo_enable_statistics=enable_statistics,
665 repo_enable_downloads=enable_downloads,
693 repo_enable_downloads=enable_downloads,
666 repo_enable_locking=enable_locking))
694 repo_enable_locking=enable_locking))
667 except validation_schema.Invalid as err:
695 except validation_schema.Invalid as err:
668 raise JSONRPCValidationError(colander_exc=err)
696 raise JSONRPCValidationError(colander_exc=err)
669
697
670 try:
698 try:
671 data = {
699 data = {
672 'owner': owner,
700 'owner': owner,
673 'repo_name': schema_data['repo_group']['repo_name_without_group'],
701 'repo_name': schema_data['repo_group']['repo_name_without_group'],
674 'repo_name_full': schema_data['repo_name'],
702 'repo_name_full': schema_data['repo_name'],
675 'repo_group': schema_data['repo_group']['repo_group_id'],
703 'repo_group': schema_data['repo_group']['repo_group_id'],
676 'repo_type': schema_data['repo_type'],
704 'repo_type': schema_data['repo_type'],
677 'repo_description': schema_data['repo_description'],
705 'repo_description': schema_data['repo_description'],
678 'repo_private': schema_data['repo_private'],
706 'repo_private': schema_data['repo_private'],
679 'clone_uri': schema_data['repo_clone_uri'],
707 'clone_uri': schema_data['repo_clone_uri'],
680 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
708 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
681 'enable_statistics': schema_data['repo_enable_statistics'],
709 'enable_statistics': schema_data['repo_enable_statistics'],
682 'enable_locking': schema_data['repo_enable_locking'],
710 'enable_locking': schema_data['repo_enable_locking'],
683 'enable_downloads': schema_data['repo_enable_downloads'],
711 'enable_downloads': schema_data['repo_enable_downloads'],
684 'repo_copy_permissions': schema_data['repo_copy_permissions'],
712 'repo_copy_permissions': schema_data['repo_copy_permissions'],
685 }
713 }
686
714
687 task = RepoModel().create(form_data=data, cur_user=owner)
715 task = RepoModel().create(form_data=data, cur_user=owner)
688 from celery.result import BaseAsyncResult
716 from celery.result import BaseAsyncResult
689 task_id = None
717 task_id = None
690 if isinstance(task, BaseAsyncResult):
718 if isinstance(task, BaseAsyncResult):
691 task_id = task.task_id
719 task_id = task.task_id
692 # no commit, it's done in RepoModel, or async via celery
720 # no commit, it's done in RepoModel, or async via celery
693 return {
721 return {
694 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
722 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
695 'success': True, # cannot return the repo data here since fork
723 'success': True, # cannot return the repo data here since fork
696 # can be done async
724 # can be done async
697 'task': task_id
725 'task': task_id
698 }
726 }
699 except Exception:
727 except Exception:
700 log.exception(
728 log.exception(
701 u"Exception while trying to create the repository %s",
729 u"Exception while trying to create the repository %s",
702 schema_data['repo_name'])
730 schema_data['repo_name'])
703 raise JSONRPCError(
731 raise JSONRPCError(
704 'failed to create repository `%s`' % (schema_data['repo_name'],))
732 'failed to create repository `%s`' % (schema_data['repo_name'],))
705
733
706
734
707 @jsonrpc_method()
735 @jsonrpc_method()
708 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
736 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
709 description=Optional('')):
737 description=Optional('')):
710 """
738 """
711 Adds an extra field to a repository.
739 Adds an extra field to a repository.
712
740
713 This command can only be run using an |authtoken| with at least
741 This command can only be run using an |authtoken| with at least
714 write permissions to the |repo|.
742 write permissions to the |repo|.
715
743
716 :param apiuser: This is filled automatically from the |authtoken|.
744 :param apiuser: This is filled automatically from the |authtoken|.
717 :type apiuser: AuthUser
745 :type apiuser: AuthUser
718 :param repoid: Set the repository name or repository id.
746 :param repoid: Set the repository name or repository id.
719 :type repoid: str or int
747 :type repoid: str or int
720 :param key: Create a unique field key for this repository.
748 :param key: Create a unique field key for this repository.
721 :type key: str
749 :type key: str
722 :param label:
750 :param label:
723 :type label: Optional(str)
751 :type label: Optional(str)
724 :param description:
752 :param description:
725 :type description: Optional(str)
753 :type description: Optional(str)
726 """
754 """
727 repo = get_repo_or_error(repoid)
755 repo = get_repo_or_error(repoid)
728 if not has_superadmin_permission(apiuser):
756 if not has_superadmin_permission(apiuser):
729 _perms = ('repository.admin',)
757 _perms = ('repository.admin',)
730 validate_repo_permissions(apiuser, repoid, repo, _perms)
758 validate_repo_permissions(apiuser, repoid, repo, _perms)
731
759
732 label = Optional.extract(label) or key
760 label = Optional.extract(label) or key
733 description = Optional.extract(description)
761 description = Optional.extract(description)
734
762
735 field = RepositoryField.get_by_key_name(key, repo)
763 field = RepositoryField.get_by_key_name(key, repo)
736 if field:
764 if field:
737 raise JSONRPCError('Field with key '
765 raise JSONRPCError('Field with key '
738 '`%s` exists for repo `%s`' % (key, repoid))
766 '`%s` exists for repo `%s`' % (key, repoid))
739
767
740 try:
768 try:
741 RepoModel().add_repo_field(repo, key, field_label=label,
769 RepoModel().add_repo_field(repo, key, field_label=label,
742 field_desc=description)
770 field_desc=description)
743 Session().commit()
771 Session().commit()
744 return {
772 return {
745 'msg': "Added new repository field `%s`" % (key,),
773 'msg': "Added new repository field `%s`" % (key,),
746 'success': True,
774 'success': True,
747 }
775 }
748 except Exception:
776 except Exception:
749 log.exception("Exception occurred while trying to add field to repo")
777 log.exception("Exception occurred while trying to add field to repo")
750 raise JSONRPCError(
778 raise JSONRPCError(
751 'failed to create new field for repository `%s`' % (repoid,))
779 'failed to create new field for repository `%s`' % (repoid,))
752
780
753
781
754 @jsonrpc_method()
782 @jsonrpc_method()
755 def remove_field_from_repo(request, apiuser, repoid, key):
783 def remove_field_from_repo(request, apiuser, repoid, key):
756 """
784 """
757 Removes an extra field from a repository.
785 Removes an extra field from a repository.
758
786
759 This command can only be run using an |authtoken| with at least
787 This command can only be run using an |authtoken| with at least
760 write permissions to the |repo|.
788 write permissions to the |repo|.
761
789
762 :param apiuser: This is filled automatically from the |authtoken|.
790 :param apiuser: This is filled automatically from the |authtoken|.
763 :type apiuser: AuthUser
791 :type apiuser: AuthUser
764 :param repoid: Set the repository name or repository ID.
792 :param repoid: Set the repository name or repository ID.
765 :type repoid: str or int
793 :type repoid: str or int
766 :param key: Set the unique field key for this repository.
794 :param key: Set the unique field key for this repository.
767 :type key: str
795 :type key: str
768 """
796 """
769
797
770 repo = get_repo_or_error(repoid)
798 repo = get_repo_or_error(repoid)
771 if not has_superadmin_permission(apiuser):
799 if not has_superadmin_permission(apiuser):
772 _perms = ('repository.admin',)
800 _perms = ('repository.admin',)
773 validate_repo_permissions(apiuser, repoid, repo, _perms)
801 validate_repo_permissions(apiuser, repoid, repo, _perms)
774
802
775 field = RepositoryField.get_by_key_name(key, repo)
803 field = RepositoryField.get_by_key_name(key, repo)
776 if not field:
804 if not field:
777 raise JSONRPCError('Field with key `%s` does not '
805 raise JSONRPCError('Field with key `%s` does not '
778 'exists for repo `%s`' % (key, repoid))
806 'exists for repo `%s`' % (key, repoid))
779
807
780 try:
808 try:
781 RepoModel().delete_repo_field(repo, field_key=key)
809 RepoModel().delete_repo_field(repo, field_key=key)
782 Session().commit()
810 Session().commit()
783 return {
811 return {
784 'msg': "Deleted repository field `%s`" % (key,),
812 'msg': "Deleted repository field `%s`" % (key,),
785 'success': True,
813 'success': True,
786 }
814 }
787 except Exception:
815 except Exception:
788 log.exception(
816 log.exception(
789 "Exception occurred while trying to delete field from repo")
817 "Exception occurred while trying to delete field from repo")
790 raise JSONRPCError(
818 raise JSONRPCError(
791 'failed to delete field for repository `%s`' % (repoid,))
819 'failed to delete field for repository `%s`' % (repoid,))
792
820
793
821
794 @jsonrpc_method()
822 @jsonrpc_method()
795 def update_repo(
823 def update_repo(
796 request, apiuser, repoid, repo_name=Optional(None),
824 request, apiuser, repoid, repo_name=Optional(None),
797 owner=Optional(OAttr('apiuser')), description=Optional(''),
825 owner=Optional(OAttr('apiuser')), description=Optional(''),
798 private=Optional(False), clone_uri=Optional(None),
826 private=Optional(False), clone_uri=Optional(None),
799 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
827 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
800 enable_statistics=Optional(False),
828 enable_statistics=Optional(False),
801 enable_locking=Optional(False),
829 enable_locking=Optional(False),
802 enable_downloads=Optional(False), fields=Optional('')):
830 enable_downloads=Optional(False), fields=Optional('')):
803 """
831 """
804 Updates a repository with the given information.
832 Updates a repository with the given information.
805
833
806 This command can only be run using an |authtoken| with at least
834 This command can only be run using an |authtoken| with at least
807 admin permissions to the |repo|.
835 admin permissions to the |repo|.
808
836
809 * If the repository name contains "/", repository will be updated
837 * If the repository name contains "/", repository will be updated
810 accordingly with a repository group or nested repository groups
838 accordingly with a repository group or nested repository groups
811
839
812 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
840 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
813 called "repo-test" and place it inside group "foo/bar".
841 called "repo-test" and place it inside group "foo/bar".
814 You have to have permissions to access and write to the last repository
842 You have to have permissions to access and write to the last repository
815 group ("bar" in this example)
843 group ("bar" in this example)
816
844
817 :param apiuser: This is filled automatically from the |authtoken|.
845 :param apiuser: This is filled automatically from the |authtoken|.
818 :type apiuser: AuthUser
846 :type apiuser: AuthUser
819 :param repoid: repository name or repository ID.
847 :param repoid: repository name or repository ID.
820 :type repoid: str or int
848 :type repoid: str or int
821 :param repo_name: Update the |repo| name, including the
849 :param repo_name: Update the |repo| name, including the
822 repository group it's in.
850 repository group it's in.
823 :type repo_name: str
851 :type repo_name: str
824 :param owner: Set the |repo| owner.
852 :param owner: Set the |repo| owner.
825 :type owner: str
853 :type owner: str
826 :param fork_of: Set the |repo| as fork of another |repo|.
854 :param fork_of: Set the |repo| as fork of another |repo|.
827 :type fork_of: str
855 :type fork_of: str
828 :param description: Update the |repo| description.
856 :param description: Update the |repo| description.
829 :type description: str
857 :type description: str
830 :param private: Set the |repo| as private. (True | False)
858 :param private: Set the |repo| as private. (True | False)
831 :type private: bool
859 :type private: bool
832 :param clone_uri: Update the |repo| clone URI.
860 :param clone_uri: Update the |repo| clone URI.
833 :type clone_uri: str
861 :type clone_uri: str
834 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
862 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
835 :type landing_rev: str
863 :type landing_rev: str
836 :param enable_statistics: Enable statistics on the |repo|, (True | False).
864 :param enable_statistics: Enable statistics on the |repo|, (True | False).
837 :type enable_statistics: bool
865 :type enable_statistics: bool
838 :param enable_locking: Enable |repo| locking.
866 :param enable_locking: Enable |repo| locking.
839 :type enable_locking: bool
867 :type enable_locking: bool
840 :param enable_downloads: Enable downloads from the |repo|, (True | False).
868 :param enable_downloads: Enable downloads from the |repo|, (True | False).
841 :type enable_downloads: bool
869 :type enable_downloads: bool
842 :param fields: Add extra fields to the |repo|. Use the following
870 :param fields: Add extra fields to the |repo|. Use the following
843 example format: ``field_key=field_val,field_key2=fieldval2``.
871 example format: ``field_key=field_val,field_key2=fieldval2``.
844 Escape ', ' with \,
872 Escape ', ' with \,
845 :type fields: str
873 :type fields: str
846 """
874 """
847
875
848 repo = get_repo_or_error(repoid)
876 repo = get_repo_or_error(repoid)
849
877
850 include_secrets = False
878 include_secrets = False
851 if not has_superadmin_permission(apiuser):
879 if not has_superadmin_permission(apiuser):
852 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
880 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
853 else:
881 else:
854 include_secrets = True
882 include_secrets = True
855
883
856 updates = dict(
884 updates = dict(
857 repo_name=repo_name
885 repo_name=repo_name
858 if not isinstance(repo_name, Optional) else repo.repo_name,
886 if not isinstance(repo_name, Optional) else repo.repo_name,
859
887
860 fork_id=fork_of
888 fork_id=fork_of
861 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
889 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
862
890
863 user=owner
891 user=owner
864 if not isinstance(owner, Optional) else repo.user.username,
892 if not isinstance(owner, Optional) else repo.user.username,
865
893
866 repo_description=description
894 repo_description=description
867 if not isinstance(description, Optional) else repo.description,
895 if not isinstance(description, Optional) else repo.description,
868
896
869 repo_private=private
897 repo_private=private
870 if not isinstance(private, Optional) else repo.private,
898 if not isinstance(private, Optional) else repo.private,
871
899
872 clone_uri=clone_uri
900 clone_uri=clone_uri
873 if not isinstance(clone_uri, Optional) else repo.clone_uri,
901 if not isinstance(clone_uri, Optional) else repo.clone_uri,
874
902
875 repo_landing_rev=landing_rev
903 repo_landing_rev=landing_rev
876 if not isinstance(landing_rev, Optional) else repo._landing_revision,
904 if not isinstance(landing_rev, Optional) else repo._landing_revision,
877
905
878 repo_enable_statistics=enable_statistics
906 repo_enable_statistics=enable_statistics
879 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
907 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
880
908
881 repo_enable_locking=enable_locking
909 repo_enable_locking=enable_locking
882 if not isinstance(enable_locking, Optional) else repo.enable_locking,
910 if not isinstance(enable_locking, Optional) else repo.enable_locking,
883
911
884 repo_enable_downloads=enable_downloads
912 repo_enable_downloads=enable_downloads
885 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
913 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
886
914
887 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
915 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
888
916
889 schema = repo_schema.RepoSchema().bind(
917 schema = repo_schema.RepoSchema().bind(
890 repo_type_options=rhodecode.BACKENDS.keys(),
918 repo_type_options=rhodecode.BACKENDS.keys(),
891 repo_ref_options=ref_choices,
919 repo_ref_options=ref_choices,
892 # user caller
920 # user caller
893 user=apiuser,
921 user=apiuser,
894 old_values=repo.get_api_data())
922 old_values=repo.get_api_data())
895 try:
923 try:
896 schema_data = schema.deserialize(dict(
924 schema_data = schema.deserialize(dict(
897 # we save old value, users cannot change type
925 # we save old value, users cannot change type
898 repo_type=repo.repo_type,
926 repo_type=repo.repo_type,
899
927
900 repo_name=updates['repo_name'],
928 repo_name=updates['repo_name'],
901 repo_owner=updates['user'],
929 repo_owner=updates['user'],
902 repo_description=updates['repo_description'],
930 repo_description=updates['repo_description'],
903 repo_clone_uri=updates['clone_uri'],
931 repo_clone_uri=updates['clone_uri'],
904 repo_fork_of=updates['fork_id'],
932 repo_fork_of=updates['fork_id'],
905 repo_private=updates['repo_private'],
933 repo_private=updates['repo_private'],
906 repo_landing_commit_ref=updates['repo_landing_rev'],
934 repo_landing_commit_ref=updates['repo_landing_rev'],
907 repo_enable_statistics=updates['repo_enable_statistics'],
935 repo_enable_statistics=updates['repo_enable_statistics'],
908 repo_enable_downloads=updates['repo_enable_downloads'],
936 repo_enable_downloads=updates['repo_enable_downloads'],
909 repo_enable_locking=updates['repo_enable_locking']))
937 repo_enable_locking=updates['repo_enable_locking']))
910 except validation_schema.Invalid as err:
938 except validation_schema.Invalid as err:
911 raise JSONRPCValidationError(colander_exc=err)
939 raise JSONRPCValidationError(colander_exc=err)
912
940
913 # save validated data back into the updates dict
941 # save validated data back into the updates dict
914 validated_updates = dict(
942 validated_updates = dict(
915 repo_name=schema_data['repo_group']['repo_name_without_group'],
943 repo_name=schema_data['repo_group']['repo_name_without_group'],
916 repo_group=schema_data['repo_group']['repo_group_id'],
944 repo_group=schema_data['repo_group']['repo_group_id'],
917
945
918 user=schema_data['repo_owner'],
946 user=schema_data['repo_owner'],
919 repo_description=schema_data['repo_description'],
947 repo_description=schema_data['repo_description'],
920 repo_private=schema_data['repo_private'],
948 repo_private=schema_data['repo_private'],
921 clone_uri=schema_data['repo_clone_uri'],
949 clone_uri=schema_data['repo_clone_uri'],
922 repo_landing_rev=schema_data['repo_landing_commit_ref'],
950 repo_landing_rev=schema_data['repo_landing_commit_ref'],
923 repo_enable_statistics=schema_data['repo_enable_statistics'],
951 repo_enable_statistics=schema_data['repo_enable_statistics'],
924 repo_enable_locking=schema_data['repo_enable_locking'],
952 repo_enable_locking=schema_data['repo_enable_locking'],
925 repo_enable_downloads=schema_data['repo_enable_downloads'],
953 repo_enable_downloads=schema_data['repo_enable_downloads'],
926 )
954 )
927
955
928 if schema_data['repo_fork_of']:
956 if schema_data['repo_fork_of']:
929 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
957 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
930 validated_updates['fork_id'] = fork_repo.repo_id
958 validated_updates['fork_id'] = fork_repo.repo_id
931
959
932 # extra fields
960 # extra fields
933 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
961 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
934 if fields:
962 if fields:
935 validated_updates.update(fields)
963 validated_updates.update(fields)
936
964
937 try:
965 try:
938 RepoModel().update(repo, **validated_updates)
966 RepoModel().update(repo, **validated_updates)
939 Session().commit()
967 Session().commit()
940 return {
968 return {
941 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
969 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
942 'repository': repo.get_api_data(include_secrets=include_secrets)
970 'repository': repo.get_api_data(include_secrets=include_secrets)
943 }
971 }
944 except Exception:
972 except Exception:
945 log.exception(
973 log.exception(
946 u"Exception while trying to update the repository %s",
974 u"Exception while trying to update the repository %s",
947 repoid)
975 repoid)
948 raise JSONRPCError('failed to update repo `%s`' % repoid)
976 raise JSONRPCError('failed to update repo `%s`' % repoid)
949
977
950
978
951 @jsonrpc_method()
979 @jsonrpc_method()
952 def fork_repo(request, apiuser, repoid, fork_name,
980 def fork_repo(request, apiuser, repoid, fork_name,
953 owner=Optional(OAttr('apiuser')),
981 owner=Optional(OAttr('apiuser')),
954 description=Optional(''),
982 description=Optional(''),
955 private=Optional(False),
983 private=Optional(False),
956 clone_uri=Optional(None),
984 clone_uri=Optional(None),
957 landing_rev=Optional('rev:tip'),
985 landing_rev=Optional('rev:tip'),
958 copy_permissions=Optional(False)):
986 copy_permissions=Optional(False)):
959 """
987 """
960 Creates a fork of the specified |repo|.
988 Creates a fork of the specified |repo|.
961
989
962 * If the fork_name contains "/", fork will be created inside
990 * If the fork_name contains "/", fork will be created inside
963 a repository group or nested repository groups
991 a repository group or nested repository groups
964
992
965 For example "foo/bar/fork-repo" will create fork called "fork-repo"
993 For example "foo/bar/fork-repo" will create fork called "fork-repo"
966 inside group "foo/bar". You have to have permissions to access and
994 inside group "foo/bar". You have to have permissions to access and
967 write to the last repository group ("bar" in this example)
995 write to the last repository group ("bar" in this example)
968
996
969 This command can only be run using an |authtoken| with minimum
997 This command can only be run using an |authtoken| with minimum
970 read permissions of the forked repo, create fork permissions for an user.
998 read permissions of the forked repo, create fork permissions for an user.
971
999
972 :param apiuser: This is filled automatically from the |authtoken|.
1000 :param apiuser: This is filled automatically from the |authtoken|.
973 :type apiuser: AuthUser
1001 :type apiuser: AuthUser
974 :param repoid: Set repository name or repository ID.
1002 :param repoid: Set repository name or repository ID.
975 :type repoid: str or int
1003 :type repoid: str or int
976 :param fork_name: Set the fork name, including it's repository group membership.
1004 :param fork_name: Set the fork name, including it's repository group membership.
977 :type fork_name: str
1005 :type fork_name: str
978 :param owner: Set the fork owner.
1006 :param owner: Set the fork owner.
979 :type owner: str
1007 :type owner: str
980 :param description: Set the fork description.
1008 :param description: Set the fork description.
981 :type description: str
1009 :type description: str
982 :param copy_permissions: Copy permissions from parent |repo|. The
1010 :param copy_permissions: Copy permissions from parent |repo|. The
983 default is False.
1011 default is False.
984 :type copy_permissions: bool
1012 :type copy_permissions: bool
985 :param private: Make the fork private. The default is False.
1013 :param private: Make the fork private. The default is False.
986 :type private: bool
1014 :type private: bool
987 :param landing_rev: Set the landing revision. The default is tip.
1015 :param landing_rev: Set the landing revision. The default is tip.
988
1016
989 Example output:
1017 Example output:
990
1018
991 .. code-block:: bash
1019 .. code-block:: bash
992
1020
993 id : <id_for_response>
1021 id : <id_for_response>
994 api_key : "<api_key>"
1022 api_key : "<api_key>"
995 args: {
1023 args: {
996 "repoid" : "<reponame or repo_id>",
1024 "repoid" : "<reponame or repo_id>",
997 "fork_name": "<forkname>",
1025 "fork_name": "<forkname>",
998 "owner": "<username or user_id = Optional(=apiuser)>",
1026 "owner": "<username or user_id = Optional(=apiuser)>",
999 "description": "<description>",
1027 "description": "<description>",
1000 "copy_permissions": "<bool>",
1028 "copy_permissions": "<bool>",
1001 "private": "<bool>",
1029 "private": "<bool>",
1002 "landing_rev": "<landing_rev>"
1030 "landing_rev": "<landing_rev>"
1003 }
1031 }
1004
1032
1005 Example error output:
1033 Example error output:
1006
1034
1007 .. code-block:: bash
1035 .. code-block:: bash
1008
1036
1009 id : <id_given_in_input>
1037 id : <id_given_in_input>
1010 result: {
1038 result: {
1011 "msg": "Created fork of `<reponame>` as `<forkname>`",
1039 "msg": "Created fork of `<reponame>` as `<forkname>`",
1012 "success": true,
1040 "success": true,
1013 "task": "<celery task id or None if done sync>"
1041 "task": "<celery task id or None if done sync>"
1014 }
1042 }
1015 error: null
1043 error: null
1016
1044
1017 """
1045 """
1018
1046
1019 repo = get_repo_or_error(repoid)
1047 repo = get_repo_or_error(repoid)
1020 repo_name = repo.repo_name
1048 repo_name = repo.repo_name
1021
1049
1022 if not has_superadmin_permission(apiuser):
1050 if not has_superadmin_permission(apiuser):
1023 # check if we have at least read permission for
1051 # check if we have at least read permission for
1024 # this repo that we fork !
1052 # this repo that we fork !
1025 _perms = (
1053 _perms = (
1026 'repository.admin', 'repository.write', 'repository.read')
1054 'repository.admin', 'repository.write', 'repository.read')
1027 validate_repo_permissions(apiuser, repoid, repo, _perms)
1055 validate_repo_permissions(apiuser, repoid, repo, _perms)
1028
1056
1029 # check if the regular user has at least fork permissions as well
1057 # check if the regular user has at least fork permissions as well
1030 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1058 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1031 raise JSONRPCForbidden()
1059 raise JSONRPCForbidden()
1032
1060
1033 # check if user can set owner parameter
1061 # check if user can set owner parameter
1034 owner = validate_set_owner_permissions(apiuser, owner)
1062 owner = validate_set_owner_permissions(apiuser, owner)
1035
1063
1036 description = Optional.extract(description)
1064 description = Optional.extract(description)
1037 copy_permissions = Optional.extract(copy_permissions)
1065 copy_permissions = Optional.extract(copy_permissions)
1038 clone_uri = Optional.extract(clone_uri)
1066 clone_uri = Optional.extract(clone_uri)
1039 landing_commit_ref = Optional.extract(landing_rev)
1067 landing_commit_ref = Optional.extract(landing_rev)
1040 private = Optional.extract(private)
1068 private = Optional.extract(private)
1041
1069
1042 schema = repo_schema.RepoSchema().bind(
1070 schema = repo_schema.RepoSchema().bind(
1043 repo_type_options=rhodecode.BACKENDS.keys(),
1071 repo_type_options=rhodecode.BACKENDS.keys(),
1044 # user caller
1072 # user caller
1045 user=apiuser)
1073 user=apiuser)
1046
1074
1047 try:
1075 try:
1048 schema_data = schema.deserialize(dict(
1076 schema_data = schema.deserialize(dict(
1049 repo_name=fork_name,
1077 repo_name=fork_name,
1050 repo_type=repo.repo_type,
1078 repo_type=repo.repo_type,
1051 repo_owner=owner.username,
1079 repo_owner=owner.username,
1052 repo_description=description,
1080 repo_description=description,
1053 repo_landing_commit_ref=landing_commit_ref,
1081 repo_landing_commit_ref=landing_commit_ref,
1054 repo_clone_uri=clone_uri,
1082 repo_clone_uri=clone_uri,
1055 repo_private=private,
1083 repo_private=private,
1056 repo_copy_permissions=copy_permissions))
1084 repo_copy_permissions=copy_permissions))
1057 except validation_schema.Invalid as err:
1085 except validation_schema.Invalid as err:
1058 raise JSONRPCValidationError(colander_exc=err)
1086 raise JSONRPCValidationError(colander_exc=err)
1059
1087
1060 try:
1088 try:
1061 data = {
1089 data = {
1062 'fork_parent_id': repo.repo_id,
1090 'fork_parent_id': repo.repo_id,
1063
1091
1064 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1092 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1065 'repo_name_full': schema_data['repo_name'],
1093 'repo_name_full': schema_data['repo_name'],
1066 'repo_group': schema_data['repo_group']['repo_group_id'],
1094 'repo_group': schema_data['repo_group']['repo_group_id'],
1067 'repo_type': schema_data['repo_type'],
1095 'repo_type': schema_data['repo_type'],
1068 'description': schema_data['repo_description'],
1096 'description': schema_data['repo_description'],
1069 'private': schema_data['repo_private'],
1097 'private': schema_data['repo_private'],
1070 'copy_permissions': schema_data['repo_copy_permissions'],
1098 'copy_permissions': schema_data['repo_copy_permissions'],
1071 'landing_rev': schema_data['repo_landing_commit_ref'],
1099 'landing_rev': schema_data['repo_landing_commit_ref'],
1072 }
1100 }
1073
1101
1074 task = RepoModel().create_fork(data, cur_user=owner)
1102 task = RepoModel().create_fork(data, cur_user=owner)
1075 # no commit, it's done in RepoModel, or async via celery
1103 # no commit, it's done in RepoModel, or async via celery
1076 from celery.result import BaseAsyncResult
1104 from celery.result import BaseAsyncResult
1077 task_id = None
1105 task_id = None
1078 if isinstance(task, BaseAsyncResult):
1106 if isinstance(task, BaseAsyncResult):
1079 task_id = task.task_id
1107 task_id = task.task_id
1080 return {
1108 return {
1081 'msg': 'Created fork of `%s` as `%s`' % (
1109 'msg': 'Created fork of `%s` as `%s`' % (
1082 repo.repo_name, schema_data['repo_name']),
1110 repo.repo_name, schema_data['repo_name']),
1083 'success': True, # cannot return the repo data here since fork
1111 'success': True, # cannot return the repo data here since fork
1084 # can be done async
1112 # can be done async
1085 'task': task_id
1113 'task': task_id
1086 }
1114 }
1087 except Exception:
1115 except Exception:
1088 log.exception(
1116 log.exception(
1089 u"Exception while trying to create fork %s",
1117 u"Exception while trying to create fork %s",
1090 schema_data['repo_name'])
1118 schema_data['repo_name'])
1091 raise JSONRPCError(
1119 raise JSONRPCError(
1092 'failed to fork repository `%s` as `%s`' % (
1120 'failed to fork repository `%s` as `%s`' % (
1093 repo_name, schema_data['repo_name']))
1121 repo_name, schema_data['repo_name']))
1094
1122
1095
1123
1096 @jsonrpc_method()
1124 @jsonrpc_method()
1097 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1125 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1098 """
1126 """
1099 Deletes a repository.
1127 Deletes a repository.
1100
1128
1101 * When the `forks` parameter is set it's possible to detach or delete
1129 * When the `forks` parameter is set it's possible to detach or delete
1102 forks of deleted repository.
1130 forks of deleted repository.
1103
1131
1104 This command can only be run using an |authtoken| with admin
1132 This command can only be run using an |authtoken| with admin
1105 permissions on the |repo|.
1133 permissions on the |repo|.
1106
1134
1107 :param apiuser: This is filled automatically from the |authtoken|.
1135 :param apiuser: This is filled automatically from the |authtoken|.
1108 :type apiuser: AuthUser
1136 :type apiuser: AuthUser
1109 :param repoid: Set the repository name or repository ID.
1137 :param repoid: Set the repository name or repository ID.
1110 :type repoid: str or int
1138 :type repoid: str or int
1111 :param forks: Set to `detach` or `delete` forks from the |repo|.
1139 :param forks: Set to `detach` or `delete` forks from the |repo|.
1112 :type forks: Optional(str)
1140 :type forks: Optional(str)
1113
1141
1114 Example error output:
1142 Example error output:
1115
1143
1116 .. code-block:: bash
1144 .. code-block:: bash
1117
1145
1118 id : <id_given_in_input>
1146 id : <id_given_in_input>
1119 result: {
1147 result: {
1120 "msg": "Deleted repository `<reponame>`",
1148 "msg": "Deleted repository `<reponame>`",
1121 "success": true
1149 "success": true
1122 }
1150 }
1123 error: null
1151 error: null
1124 """
1152 """
1125
1153
1126 repo = get_repo_or_error(repoid)
1154 repo = get_repo_or_error(repoid)
1127 if not has_superadmin_permission(apiuser):
1155 if not has_superadmin_permission(apiuser):
1128 _perms = ('repository.admin',)
1156 _perms = ('repository.admin',)
1129 validate_repo_permissions(apiuser, repoid, repo, _perms)
1157 validate_repo_permissions(apiuser, repoid, repo, _perms)
1130
1158
1131 try:
1159 try:
1132 handle_forks = Optional.extract(forks)
1160 handle_forks = Optional.extract(forks)
1133 _forks_msg = ''
1161 _forks_msg = ''
1134 _forks = [f for f in repo.forks]
1162 _forks = [f for f in repo.forks]
1135 if handle_forks == 'detach':
1163 if handle_forks == 'detach':
1136 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1164 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1137 elif handle_forks == 'delete':
1165 elif handle_forks == 'delete':
1138 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1166 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1139 elif _forks:
1167 elif _forks:
1140 raise JSONRPCError(
1168 raise JSONRPCError(
1141 'Cannot delete `%s` it still contains attached forks' %
1169 'Cannot delete `%s` it still contains attached forks' %
1142 (repo.repo_name,)
1170 (repo.repo_name,)
1143 )
1171 )
1144
1172
1145 RepoModel().delete(repo, forks=forks)
1173 RepoModel().delete(repo, forks=forks)
1146 Session().commit()
1174 Session().commit()
1147 return {
1175 return {
1148 'msg': 'Deleted repository `%s`%s' % (
1176 'msg': 'Deleted repository `%s`%s' % (
1149 repo.repo_name, _forks_msg),
1177 repo.repo_name, _forks_msg),
1150 'success': True
1178 'success': True
1151 }
1179 }
1152 except Exception:
1180 except Exception:
1153 log.exception("Exception occurred while trying to delete repo")
1181 log.exception("Exception occurred while trying to delete repo")
1154 raise JSONRPCError(
1182 raise JSONRPCError(
1155 'failed to delete repository `%s`' % (repo.repo_name,)
1183 'failed to delete repository `%s`' % (repo.repo_name,)
1156 )
1184 )
1157
1185
1158
1186
1159 #TODO: marcink, change name ?
1187 #TODO: marcink, change name ?
1160 @jsonrpc_method()
1188 @jsonrpc_method()
1161 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1189 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1162 """
1190 """
1163 Invalidates the cache for the specified repository.
1191 Invalidates the cache for the specified repository.
1164
1192
1165 This command can only be run using an |authtoken| with admin rights to
1193 This command can only be run using an |authtoken| with admin rights to
1166 the specified repository.
1194 the specified repository.
1167
1195
1168 This command takes the following options:
1196 This command takes the following options:
1169
1197
1170 :param apiuser: This is filled automatically from |authtoken|.
1198 :param apiuser: This is filled automatically from |authtoken|.
1171 :type apiuser: AuthUser
1199 :type apiuser: AuthUser
1172 :param repoid: Sets the repository name or repository ID.
1200 :param repoid: Sets the repository name or repository ID.
1173 :type repoid: str or int
1201 :type repoid: str or int
1174 :param delete_keys: This deletes the invalidated keys instead of
1202 :param delete_keys: This deletes the invalidated keys instead of
1175 just flagging them.
1203 just flagging them.
1176 :type delete_keys: Optional(``True`` | ``False``)
1204 :type delete_keys: Optional(``True`` | ``False``)
1177
1205
1178 Example output:
1206 Example output:
1179
1207
1180 .. code-block:: bash
1208 .. code-block:: bash
1181
1209
1182 id : <id_given_in_input>
1210 id : <id_given_in_input>
1183 result : {
1211 result : {
1184 'msg': Cache for repository `<repository name>` was invalidated,
1212 'msg': Cache for repository `<repository name>` was invalidated,
1185 'repository': <repository name>
1213 'repository': <repository name>
1186 }
1214 }
1187 error : null
1215 error : null
1188
1216
1189 Example error output:
1217 Example error output:
1190
1218
1191 .. code-block:: bash
1219 .. code-block:: bash
1192
1220
1193 id : <id_given_in_input>
1221 id : <id_given_in_input>
1194 result : null
1222 result : null
1195 error : {
1223 error : {
1196 'Error occurred during cache invalidation action'
1224 'Error occurred during cache invalidation action'
1197 }
1225 }
1198
1226
1199 """
1227 """
1200
1228
1201 repo = get_repo_or_error(repoid)
1229 repo = get_repo_or_error(repoid)
1202 if not has_superadmin_permission(apiuser):
1230 if not has_superadmin_permission(apiuser):
1203 _perms = ('repository.admin', 'repository.write',)
1231 _perms = ('repository.admin', 'repository.write',)
1204 validate_repo_permissions(apiuser, repoid, repo, _perms)
1232 validate_repo_permissions(apiuser, repoid, repo, _perms)
1205
1233
1206 delete = Optional.extract(delete_keys)
1234 delete = Optional.extract(delete_keys)
1207 try:
1235 try:
1208 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1236 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1209 return {
1237 return {
1210 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1238 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1211 'repository': repo.repo_name
1239 'repository': repo.repo_name
1212 }
1240 }
1213 except Exception:
1241 except Exception:
1214 log.exception(
1242 log.exception(
1215 "Exception occurred while trying to invalidate repo cache")
1243 "Exception occurred while trying to invalidate repo cache")
1216 raise JSONRPCError(
1244 raise JSONRPCError(
1217 'Error occurred during cache invalidation action'
1245 'Error occurred during cache invalidation action'
1218 )
1246 )
1219
1247
1220
1248
1221 #TODO: marcink, change name ?
1249 #TODO: marcink, change name ?
1222 @jsonrpc_method()
1250 @jsonrpc_method()
1223 def lock(request, apiuser, repoid, locked=Optional(None),
1251 def lock(request, apiuser, repoid, locked=Optional(None),
1224 userid=Optional(OAttr('apiuser'))):
1252 userid=Optional(OAttr('apiuser'))):
1225 """
1253 """
1226 Sets the lock state of the specified |repo| by the given user.
1254 Sets the lock state of the specified |repo| by the given user.
1227 From more information, see :ref:`repo-locking`.
1255 From more information, see :ref:`repo-locking`.
1228
1256
1229 * If the ``userid`` option is not set, the repository is locked to the
1257 * If the ``userid`` option is not set, the repository is locked to the
1230 user who called the method.
1258 user who called the method.
1231 * If the ``locked`` parameter is not set, the current lock state of the
1259 * If the ``locked`` parameter is not set, the current lock state of the
1232 repository is displayed.
1260 repository is displayed.
1233
1261
1234 This command can only be run using an |authtoken| with admin rights to
1262 This command can only be run using an |authtoken| with admin rights to
1235 the specified repository.
1263 the specified repository.
1236
1264
1237 This command takes the following options:
1265 This command takes the following options:
1238
1266
1239 :param apiuser: This is filled automatically from the |authtoken|.
1267 :param apiuser: This is filled automatically from the |authtoken|.
1240 :type apiuser: AuthUser
1268 :type apiuser: AuthUser
1241 :param repoid: Sets the repository name or repository ID.
1269 :param repoid: Sets the repository name or repository ID.
1242 :type repoid: str or int
1270 :type repoid: str or int
1243 :param locked: Sets the lock state.
1271 :param locked: Sets the lock state.
1244 :type locked: Optional(``True`` | ``False``)
1272 :type locked: Optional(``True`` | ``False``)
1245 :param userid: Set the repository lock to this user.
1273 :param userid: Set the repository lock to this user.
1246 :type userid: Optional(str or int)
1274 :type userid: Optional(str or int)
1247
1275
1248 Example error output:
1276 Example error output:
1249
1277
1250 .. code-block:: bash
1278 .. code-block:: bash
1251
1279
1252 id : <id_given_in_input>
1280 id : <id_given_in_input>
1253 result : {
1281 result : {
1254 'repo': '<reponame>',
1282 'repo': '<reponame>',
1255 'locked': <bool: lock state>,
1283 'locked': <bool: lock state>,
1256 'locked_since': <int: lock timestamp>,
1284 'locked_since': <int: lock timestamp>,
1257 'locked_by': <username of person who made the lock>,
1285 'locked_by': <username of person who made the lock>,
1258 'lock_reason': <str: reason for locking>,
1286 'lock_reason': <str: reason for locking>,
1259 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1287 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1260 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1288 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1261 or
1289 or
1262 'msg': 'Repo `<repository name>` not locked.'
1290 'msg': 'Repo `<repository name>` not locked.'
1263 or
1291 or
1264 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1292 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1265 }
1293 }
1266 error : null
1294 error : null
1267
1295
1268 Example error output:
1296 Example error output:
1269
1297
1270 .. code-block:: bash
1298 .. code-block:: bash
1271
1299
1272 id : <id_given_in_input>
1300 id : <id_given_in_input>
1273 result : null
1301 result : null
1274 error : {
1302 error : {
1275 'Error occurred locking repository `<reponame>`'
1303 'Error occurred locking repository `<reponame>`'
1276 }
1304 }
1277 """
1305 """
1278
1306
1279 repo = get_repo_or_error(repoid)
1307 repo = get_repo_or_error(repoid)
1280 if not has_superadmin_permission(apiuser):
1308 if not has_superadmin_permission(apiuser):
1281 # check if we have at least write permission for this repo !
1309 # check if we have at least write permission for this repo !
1282 _perms = ('repository.admin', 'repository.write',)
1310 _perms = ('repository.admin', 'repository.write',)
1283 validate_repo_permissions(apiuser, repoid, repo, _perms)
1311 validate_repo_permissions(apiuser, repoid, repo, _perms)
1284
1312
1285 # make sure normal user does not pass someone else userid,
1313 # make sure normal user does not pass someone else userid,
1286 # he is not allowed to do that
1314 # he is not allowed to do that
1287 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1315 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1288 raise JSONRPCError('userid is not the same as your user')
1316 raise JSONRPCError('userid is not the same as your user')
1289
1317
1290 if isinstance(userid, Optional):
1318 if isinstance(userid, Optional):
1291 userid = apiuser.user_id
1319 userid = apiuser.user_id
1292
1320
1293 user = get_user_or_error(userid)
1321 user = get_user_or_error(userid)
1294
1322
1295 if isinstance(locked, Optional):
1323 if isinstance(locked, Optional):
1296 lockobj = repo.locked
1324 lockobj = repo.locked
1297
1325
1298 if lockobj[0] is None:
1326 if lockobj[0] is None:
1299 _d = {
1327 _d = {
1300 'repo': repo.repo_name,
1328 'repo': repo.repo_name,
1301 'locked': False,
1329 'locked': False,
1302 'locked_since': None,
1330 'locked_since': None,
1303 'locked_by': None,
1331 'locked_by': None,
1304 'lock_reason': None,
1332 'lock_reason': None,
1305 'lock_state_changed': False,
1333 'lock_state_changed': False,
1306 'msg': 'Repo `%s` not locked.' % repo.repo_name
1334 'msg': 'Repo `%s` not locked.' % repo.repo_name
1307 }
1335 }
1308 return _d
1336 return _d
1309 else:
1337 else:
1310 _user_id, _time, _reason = lockobj
1338 _user_id, _time, _reason = lockobj
1311 lock_user = get_user_or_error(userid)
1339 lock_user = get_user_or_error(userid)
1312 _d = {
1340 _d = {
1313 'repo': repo.repo_name,
1341 'repo': repo.repo_name,
1314 'locked': True,
1342 'locked': True,
1315 'locked_since': _time,
1343 'locked_since': _time,
1316 'locked_by': lock_user.username,
1344 'locked_by': lock_user.username,
1317 'lock_reason': _reason,
1345 'lock_reason': _reason,
1318 'lock_state_changed': False,
1346 'lock_state_changed': False,
1319 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1347 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1320 % (repo.repo_name, lock_user.username,
1348 % (repo.repo_name, lock_user.username,
1321 json.dumps(time_to_datetime(_time))))
1349 json.dumps(time_to_datetime(_time))))
1322 }
1350 }
1323 return _d
1351 return _d
1324
1352
1325 # force locked state through a flag
1353 # force locked state through a flag
1326 else:
1354 else:
1327 locked = str2bool(locked)
1355 locked = str2bool(locked)
1328 lock_reason = Repository.LOCK_API
1356 lock_reason = Repository.LOCK_API
1329 try:
1357 try:
1330 if locked:
1358 if locked:
1331 lock_time = time.time()
1359 lock_time = time.time()
1332 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1360 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1333 else:
1361 else:
1334 lock_time = None
1362 lock_time = None
1335 Repository.unlock(repo)
1363 Repository.unlock(repo)
1336 _d = {
1364 _d = {
1337 'repo': repo.repo_name,
1365 'repo': repo.repo_name,
1338 'locked': locked,
1366 'locked': locked,
1339 'locked_since': lock_time,
1367 'locked_since': lock_time,
1340 'locked_by': user.username,
1368 'locked_by': user.username,
1341 'lock_reason': lock_reason,
1369 'lock_reason': lock_reason,
1342 'lock_state_changed': True,
1370 'lock_state_changed': True,
1343 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1371 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1344 % (user.username, repo.repo_name, locked))
1372 % (user.username, repo.repo_name, locked))
1345 }
1373 }
1346 return _d
1374 return _d
1347 except Exception:
1375 except Exception:
1348 log.exception(
1376 log.exception(
1349 "Exception occurred while trying to lock repository")
1377 "Exception occurred while trying to lock repository")
1350 raise JSONRPCError(
1378 raise JSONRPCError(
1351 'Error occurred locking repository `%s`' % repo.repo_name
1379 'Error occurred locking repository `%s`' % repo.repo_name
1352 )
1380 )
1353
1381
1354
1382
1355 @jsonrpc_method()
1383 @jsonrpc_method()
1356 def comment_commit(
1384 def comment_commit(
1357 request, apiuser, repoid, commit_id, message,
1385 request, apiuser, repoid, commit_id, message,
1358 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1386 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1359 """
1387 """
1360 Set a commit comment, and optionally change the status of the commit.
1388 Set a commit comment, and optionally change the status of the commit.
1361
1389
1362 :param apiuser: This is filled automatically from the |authtoken|.
1390 :param apiuser: This is filled automatically from the |authtoken|.
1363 :type apiuser: AuthUser
1391 :type apiuser: AuthUser
1364 :param repoid: Set the repository name or repository ID.
1392 :param repoid: Set the repository name or repository ID.
1365 :type repoid: str or int
1393 :type repoid: str or int
1366 :param commit_id: Specify the commit_id for which to set a comment.
1394 :param commit_id: Specify the commit_id for which to set a comment.
1367 :type commit_id: str
1395 :type commit_id: str
1368 :param message: The comment text.
1396 :param message: The comment text.
1369 :type message: str
1397 :type message: str
1370 :param userid: Set the user name of the comment creator.
1398 :param userid: Set the user name of the comment creator.
1371 :type userid: Optional(str or int)
1399 :type userid: Optional(str or int)
1372 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
1400 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
1373 'under_review'
1401 'under_review'
1374 :type status: str
1402 :type status: str
1375
1403
1376 Example error output:
1404 Example error output:
1377
1405
1378 .. code-block:: json
1406 .. code-block:: json
1379
1407
1380 {
1408 {
1381 "id" : <id_given_in_input>,
1409 "id" : <id_given_in_input>,
1382 "result" : {
1410 "result" : {
1383 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1411 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1384 "status_change": null or <status>,
1412 "status_change": null or <status>,
1385 "success": true
1413 "success": true
1386 },
1414 },
1387 "error" : null
1415 "error" : null
1388 }
1416 }
1389
1417
1390 """
1418 """
1391 repo = get_repo_or_error(repoid)
1419 repo = get_repo_or_error(repoid)
1392 if not has_superadmin_permission(apiuser):
1420 if not has_superadmin_permission(apiuser):
1393 _perms = ('repository.read', 'repository.write', 'repository.admin')
1421 _perms = ('repository.read', 'repository.write', 'repository.admin')
1394 validate_repo_permissions(apiuser, repoid, repo, _perms)
1422 validate_repo_permissions(apiuser, repoid, repo, _perms)
1395
1423
1396 if isinstance(userid, Optional):
1424 if isinstance(userid, Optional):
1397 userid = apiuser.user_id
1425 userid = apiuser.user_id
1398
1426
1399 user = get_user_or_error(userid)
1427 user = get_user_or_error(userid)
1400 status = Optional.extract(status)
1428 status = Optional.extract(status)
1401
1429
1402 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1430 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1403 if status and status not in allowed_statuses:
1431 if status and status not in allowed_statuses:
1404 raise JSONRPCError('Bad status, must be on '
1432 raise JSONRPCError('Bad status, must be on '
1405 'of %s got %s' % (allowed_statuses, status,))
1433 'of %s got %s' % (allowed_statuses, status,))
1406
1434
1407 try:
1435 try:
1408 rc_config = SettingsModel().get_all_settings()
1436 rc_config = SettingsModel().get_all_settings()
1409 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1437 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1410 status_change_label = ChangesetStatus.get_status_lbl(status)
1438 status_change_label = ChangesetStatus.get_status_lbl(status)
1411 comm = ChangesetCommentsModel().create(
1439 comm = ChangesetCommentsModel().create(
1412 message, repo, user, revision=commit_id,
1440 message, repo, user, revision=commit_id,
1413 status_change=status_change_label,
1441 status_change=status_change_label,
1414 status_change_type=status,
1442 status_change_type=status,
1415 renderer=renderer)
1443 renderer=renderer)
1416 if status:
1444 if status:
1417 # also do a status change
1445 # also do a status change
1418 try:
1446 try:
1419 ChangesetStatusModel().set_status(
1447 ChangesetStatusModel().set_status(
1420 repo, status, user, comm, revision=commit_id,
1448 repo, status, user, comm, revision=commit_id,
1421 dont_allow_on_closed_pull_request=True
1449 dont_allow_on_closed_pull_request=True
1422 )
1450 )
1423 except StatusChangeOnClosedPullRequestError:
1451 except StatusChangeOnClosedPullRequestError:
1424 log.exception(
1452 log.exception(
1425 "Exception occurred while trying to change repo commit status")
1453 "Exception occurred while trying to change repo commit status")
1426 msg = ('Changing status on a changeset associated with '
1454 msg = ('Changing status on a changeset associated with '
1427 'a closed pull request is not allowed')
1455 'a closed pull request is not allowed')
1428 raise JSONRPCError(msg)
1456 raise JSONRPCError(msg)
1429
1457
1430 Session().commit()
1458 Session().commit()
1431 return {
1459 return {
1432 'msg': (
1460 'msg': (
1433 'Commented on commit `%s` for repository `%s`' % (
1461 'Commented on commit `%s` for repository `%s`' % (
1434 comm.revision, repo.repo_name)),
1462 comm.revision, repo.repo_name)),
1435 'status_change': status,
1463 'status_change': status,
1436 'success': True,
1464 'success': True,
1437 }
1465 }
1438 except JSONRPCError:
1466 except JSONRPCError:
1439 # catch any inside errors, and re-raise them to prevent from
1467 # catch any inside errors, and re-raise them to prevent from
1440 # below global catch to silence them
1468 # below global catch to silence them
1441 raise
1469 raise
1442 except Exception:
1470 except Exception:
1443 log.exception("Exception occurred while trying to comment on commit")
1471 log.exception("Exception occurred while trying to comment on commit")
1444 raise JSONRPCError(
1472 raise JSONRPCError(
1445 'failed to set comment on repository `%s`' % (repo.repo_name,)
1473 'failed to set comment on repository `%s`' % (repo.repo_name,)
1446 )
1474 )
1447
1475
1448
1476
1449 @jsonrpc_method()
1477 @jsonrpc_method()
1450 def grant_user_permission(request, apiuser, repoid, userid, perm):
1478 def grant_user_permission(request, apiuser, repoid, userid, perm):
1451 """
1479 """
1452 Grant permissions for the specified user on the given repository,
1480 Grant permissions for the specified user on the given repository,
1453 or update existing permissions if found.
1481 or update existing permissions if found.
1454
1482
1455 This command can only be run using an |authtoken| with admin
1483 This command can only be run using an |authtoken| with admin
1456 permissions on the |repo|.
1484 permissions on the |repo|.
1457
1485
1458 :param apiuser: This is filled automatically from the |authtoken|.
1486 :param apiuser: This is filled automatically from the |authtoken|.
1459 :type apiuser: AuthUser
1487 :type apiuser: AuthUser
1460 :param repoid: Set the repository name or repository ID.
1488 :param repoid: Set the repository name or repository ID.
1461 :type repoid: str or int
1489 :type repoid: str or int
1462 :param userid: Set the user name.
1490 :param userid: Set the user name.
1463 :type userid: str
1491 :type userid: str
1464 :param perm: Set the user permissions, using the following format
1492 :param perm: Set the user permissions, using the following format
1465 ``(repository.(none|read|write|admin))``
1493 ``(repository.(none|read|write|admin))``
1466 :type perm: str
1494 :type perm: str
1467
1495
1468 Example output:
1496 Example output:
1469
1497
1470 .. code-block:: bash
1498 .. code-block:: bash
1471
1499
1472 id : <id_given_in_input>
1500 id : <id_given_in_input>
1473 result: {
1501 result: {
1474 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1502 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1475 "success": true
1503 "success": true
1476 }
1504 }
1477 error: null
1505 error: null
1478 """
1506 """
1479
1507
1480 repo = get_repo_or_error(repoid)
1508 repo = get_repo_or_error(repoid)
1481 user = get_user_or_error(userid)
1509 user = get_user_or_error(userid)
1482 perm = get_perm_or_error(perm)
1510 perm = get_perm_or_error(perm)
1483 if not has_superadmin_permission(apiuser):
1511 if not has_superadmin_permission(apiuser):
1484 _perms = ('repository.admin',)
1512 _perms = ('repository.admin',)
1485 validate_repo_permissions(apiuser, repoid, repo, _perms)
1513 validate_repo_permissions(apiuser, repoid, repo, _perms)
1486
1514
1487 try:
1515 try:
1488
1516
1489 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1517 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1490
1518
1491 Session().commit()
1519 Session().commit()
1492 return {
1520 return {
1493 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1521 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1494 perm.permission_name, user.username, repo.repo_name
1522 perm.permission_name, user.username, repo.repo_name
1495 ),
1523 ),
1496 'success': True
1524 'success': True
1497 }
1525 }
1498 except Exception:
1526 except Exception:
1499 log.exception(
1527 log.exception(
1500 "Exception occurred while trying edit permissions for repo")
1528 "Exception occurred while trying edit permissions for repo")
1501 raise JSONRPCError(
1529 raise JSONRPCError(
1502 'failed to edit permission for user: `%s` in repo: `%s`' % (
1530 'failed to edit permission for user: `%s` in repo: `%s`' % (
1503 userid, repoid
1531 userid, repoid
1504 )
1532 )
1505 )
1533 )
1506
1534
1507
1535
1508 @jsonrpc_method()
1536 @jsonrpc_method()
1509 def revoke_user_permission(request, apiuser, repoid, userid):
1537 def revoke_user_permission(request, apiuser, repoid, userid):
1510 """
1538 """
1511 Revoke permission for a user on the specified repository.
1539 Revoke permission for a user on the specified repository.
1512
1540
1513 This command can only be run using an |authtoken| with admin
1541 This command can only be run using an |authtoken| with admin
1514 permissions on the |repo|.
1542 permissions on the |repo|.
1515
1543
1516 :param apiuser: This is filled automatically from the |authtoken|.
1544 :param apiuser: This is filled automatically from the |authtoken|.
1517 :type apiuser: AuthUser
1545 :type apiuser: AuthUser
1518 :param repoid: Set the repository name or repository ID.
1546 :param repoid: Set the repository name or repository ID.
1519 :type repoid: str or int
1547 :type repoid: str or int
1520 :param userid: Set the user name of revoked user.
1548 :param userid: Set the user name of revoked user.
1521 :type userid: str or int
1549 :type userid: str or int
1522
1550
1523 Example error output:
1551 Example error output:
1524
1552
1525 .. code-block:: bash
1553 .. code-block:: bash
1526
1554
1527 id : <id_given_in_input>
1555 id : <id_given_in_input>
1528 result: {
1556 result: {
1529 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1557 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1530 "success": true
1558 "success": true
1531 }
1559 }
1532 error: null
1560 error: null
1533 """
1561 """
1534
1562
1535 repo = get_repo_or_error(repoid)
1563 repo = get_repo_or_error(repoid)
1536 user = get_user_or_error(userid)
1564 user = get_user_or_error(userid)
1537 if not has_superadmin_permission(apiuser):
1565 if not has_superadmin_permission(apiuser):
1538 _perms = ('repository.admin',)
1566 _perms = ('repository.admin',)
1539 validate_repo_permissions(apiuser, repoid, repo, _perms)
1567 validate_repo_permissions(apiuser, repoid, repo, _perms)
1540
1568
1541 try:
1569 try:
1542 RepoModel().revoke_user_permission(repo=repo, user=user)
1570 RepoModel().revoke_user_permission(repo=repo, user=user)
1543 Session().commit()
1571 Session().commit()
1544 return {
1572 return {
1545 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1573 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1546 user.username, repo.repo_name
1574 user.username, repo.repo_name
1547 ),
1575 ),
1548 'success': True
1576 'success': True
1549 }
1577 }
1550 except Exception:
1578 except Exception:
1551 log.exception(
1579 log.exception(
1552 "Exception occurred while trying revoke permissions to repo")
1580 "Exception occurred while trying revoke permissions to repo")
1553 raise JSONRPCError(
1581 raise JSONRPCError(
1554 'failed to edit permission for user: `%s` in repo: `%s`' % (
1582 'failed to edit permission for user: `%s` in repo: `%s`' % (
1555 userid, repoid
1583 userid, repoid
1556 )
1584 )
1557 )
1585 )
1558
1586
1559
1587
1560 @jsonrpc_method()
1588 @jsonrpc_method()
1561 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1589 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1562 """
1590 """
1563 Grant permission for a user group on the specified repository,
1591 Grant permission for a user group on the specified repository,
1564 or update existing permissions.
1592 or update existing permissions.
1565
1593
1566 This command can only be run using an |authtoken| with admin
1594 This command can only be run using an |authtoken| with admin
1567 permissions on the |repo|.
1595 permissions on the |repo|.
1568
1596
1569 :param apiuser: This is filled automatically from the |authtoken|.
1597 :param apiuser: This is filled automatically from the |authtoken|.
1570 :type apiuser: AuthUser
1598 :type apiuser: AuthUser
1571 :param repoid: Set the repository name or repository ID.
1599 :param repoid: Set the repository name or repository ID.
1572 :type repoid: str or int
1600 :type repoid: str or int
1573 :param usergroupid: Specify the ID of the user group.
1601 :param usergroupid: Specify the ID of the user group.
1574 :type usergroupid: str or int
1602 :type usergroupid: str or int
1575 :param perm: Set the user group permissions using the following
1603 :param perm: Set the user group permissions using the following
1576 format: (repository.(none|read|write|admin))
1604 format: (repository.(none|read|write|admin))
1577 :type perm: str
1605 :type perm: str
1578
1606
1579 Example output:
1607 Example output:
1580
1608
1581 .. code-block:: bash
1609 .. code-block:: bash
1582
1610
1583 id : <id_given_in_input>
1611 id : <id_given_in_input>
1584 result : {
1612 result : {
1585 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1613 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1586 "success": true
1614 "success": true
1587
1615
1588 }
1616 }
1589 error : null
1617 error : null
1590
1618
1591 Example error output:
1619 Example error output:
1592
1620
1593 .. code-block:: bash
1621 .. code-block:: bash
1594
1622
1595 id : <id_given_in_input>
1623 id : <id_given_in_input>
1596 result : null
1624 result : null
1597 error : {
1625 error : {
1598 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1626 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1599 }
1627 }
1600
1628
1601 """
1629 """
1602
1630
1603 repo = get_repo_or_error(repoid)
1631 repo = get_repo_or_error(repoid)
1604 perm = get_perm_or_error(perm)
1632 perm = get_perm_or_error(perm)
1605 if not has_superadmin_permission(apiuser):
1633 if not has_superadmin_permission(apiuser):
1606 _perms = ('repository.admin',)
1634 _perms = ('repository.admin',)
1607 validate_repo_permissions(apiuser, repoid, repo, _perms)
1635 validate_repo_permissions(apiuser, repoid, repo, _perms)
1608
1636
1609 user_group = get_user_group_or_error(usergroupid)
1637 user_group = get_user_group_or_error(usergroupid)
1610 if not has_superadmin_permission(apiuser):
1638 if not has_superadmin_permission(apiuser):
1611 # check if we have at least read permission for this user group !
1639 # check if we have at least read permission for this user group !
1612 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1640 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1613 if not HasUserGroupPermissionAnyApi(*_perms)(
1641 if not HasUserGroupPermissionAnyApi(*_perms)(
1614 user=apiuser, user_group_name=user_group.users_group_name):
1642 user=apiuser, user_group_name=user_group.users_group_name):
1615 raise JSONRPCError(
1643 raise JSONRPCError(
1616 'user group `%s` does not exist' % (usergroupid,))
1644 'user group `%s` does not exist' % (usergroupid,))
1617
1645
1618 try:
1646 try:
1619 RepoModel().grant_user_group_permission(
1647 RepoModel().grant_user_group_permission(
1620 repo=repo, group_name=user_group, perm=perm)
1648 repo=repo, group_name=user_group, perm=perm)
1621
1649
1622 Session().commit()
1650 Session().commit()
1623 return {
1651 return {
1624 'msg': 'Granted perm: `%s` for user group: `%s` in '
1652 'msg': 'Granted perm: `%s` for user group: `%s` in '
1625 'repo: `%s`' % (
1653 'repo: `%s`' % (
1626 perm.permission_name, user_group.users_group_name,
1654 perm.permission_name, user_group.users_group_name,
1627 repo.repo_name
1655 repo.repo_name
1628 ),
1656 ),
1629 'success': True
1657 'success': True
1630 }
1658 }
1631 except Exception:
1659 except Exception:
1632 log.exception(
1660 log.exception(
1633 "Exception occurred while trying change permission on repo")
1661 "Exception occurred while trying change permission on repo")
1634 raise JSONRPCError(
1662 raise JSONRPCError(
1635 'failed to edit permission for user group: `%s` in '
1663 'failed to edit permission for user group: `%s` in '
1636 'repo: `%s`' % (
1664 'repo: `%s`' % (
1637 usergroupid, repo.repo_name
1665 usergroupid, repo.repo_name
1638 )
1666 )
1639 )
1667 )
1640
1668
1641
1669
1642 @jsonrpc_method()
1670 @jsonrpc_method()
1643 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1671 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1644 """
1672 """
1645 Revoke the permissions of a user group on a given repository.
1673 Revoke the permissions of a user group on a given repository.
1646
1674
1647 This command can only be run using an |authtoken| with admin
1675 This command can only be run using an |authtoken| with admin
1648 permissions on the |repo|.
1676 permissions on the |repo|.
1649
1677
1650 :param apiuser: This is filled automatically from the |authtoken|.
1678 :param apiuser: This is filled automatically from the |authtoken|.
1651 :type apiuser: AuthUser
1679 :type apiuser: AuthUser
1652 :param repoid: Set the repository name or repository ID.
1680 :param repoid: Set the repository name or repository ID.
1653 :type repoid: str or int
1681 :type repoid: str or int
1654 :param usergroupid: Specify the user group ID.
1682 :param usergroupid: Specify the user group ID.
1655 :type usergroupid: str or int
1683 :type usergroupid: str or int
1656
1684
1657 Example output:
1685 Example output:
1658
1686
1659 .. code-block:: bash
1687 .. code-block:: bash
1660
1688
1661 id : <id_given_in_input>
1689 id : <id_given_in_input>
1662 result: {
1690 result: {
1663 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1691 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1664 "success": true
1692 "success": true
1665 }
1693 }
1666 error: null
1694 error: null
1667 """
1695 """
1668
1696
1669 repo = get_repo_or_error(repoid)
1697 repo = get_repo_or_error(repoid)
1670 if not has_superadmin_permission(apiuser):
1698 if not has_superadmin_permission(apiuser):
1671 _perms = ('repository.admin',)
1699 _perms = ('repository.admin',)
1672 validate_repo_permissions(apiuser, repoid, repo, _perms)
1700 validate_repo_permissions(apiuser, repoid, repo, _perms)
1673
1701
1674 user_group = get_user_group_or_error(usergroupid)
1702 user_group = get_user_group_or_error(usergroupid)
1675 if not has_superadmin_permission(apiuser):
1703 if not has_superadmin_permission(apiuser):
1676 # check if we have at least read permission for this user group !
1704 # check if we have at least read permission for this user group !
1677 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1705 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1678 if not HasUserGroupPermissionAnyApi(*_perms)(
1706 if not HasUserGroupPermissionAnyApi(*_perms)(
1679 user=apiuser, user_group_name=user_group.users_group_name):
1707 user=apiuser, user_group_name=user_group.users_group_name):
1680 raise JSONRPCError(
1708 raise JSONRPCError(
1681 'user group `%s` does not exist' % (usergroupid,))
1709 'user group `%s` does not exist' % (usergroupid,))
1682
1710
1683 try:
1711 try:
1684 RepoModel().revoke_user_group_permission(
1712 RepoModel().revoke_user_group_permission(
1685 repo=repo, group_name=user_group)
1713 repo=repo, group_name=user_group)
1686
1714
1687 Session().commit()
1715 Session().commit()
1688 return {
1716 return {
1689 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1717 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1690 user_group.users_group_name, repo.repo_name
1718 user_group.users_group_name, repo.repo_name
1691 ),
1719 ),
1692 'success': True
1720 'success': True
1693 }
1721 }
1694 except Exception:
1722 except Exception:
1695 log.exception("Exception occurred while trying revoke "
1723 log.exception("Exception occurred while trying revoke "
1696 "user group permission on repo")
1724 "user group permission on repo")
1697 raise JSONRPCError(
1725 raise JSONRPCError(
1698 'failed to edit permission for user group: `%s` in '
1726 'failed to edit permission for user group: `%s` in '
1699 'repo: `%s`' % (
1727 'repo: `%s`' % (
1700 user_group.users_group_name, repo.repo_name
1728 user_group.users_group_name, repo.repo_name
1701 )
1729 )
1702 )
1730 )
1703
1731
1704
1732
1705 @jsonrpc_method()
1733 @jsonrpc_method()
1706 def pull(request, apiuser, repoid):
1734 def pull(request, apiuser, repoid):
1707 """
1735 """
1708 Triggers a pull on the given repository from a remote location. You
1736 Triggers a pull on the given repository from a remote location. You
1709 can use this to keep remote repositories up-to-date.
1737 can use this to keep remote repositories up-to-date.
1710
1738
1711 This command can only be run using an |authtoken| with admin
1739 This command can only be run using an |authtoken| with admin
1712 rights to the specified repository. For more information,
1740 rights to the specified repository. For more information,
1713 see :ref:`config-token-ref`.
1741 see :ref:`config-token-ref`.
1714
1742
1715 This command takes the following options:
1743 This command takes the following options:
1716
1744
1717 :param apiuser: This is filled automatically from the |authtoken|.
1745 :param apiuser: This is filled automatically from the |authtoken|.
1718 :type apiuser: AuthUser
1746 :type apiuser: AuthUser
1719 :param repoid: The repository name or repository ID.
1747 :param repoid: The repository name or repository ID.
1720 :type repoid: str or int
1748 :type repoid: str or int
1721
1749
1722 Example output:
1750 Example output:
1723
1751
1724 .. code-block:: bash
1752 .. code-block:: bash
1725
1753
1726 id : <id_given_in_input>
1754 id : <id_given_in_input>
1727 result : {
1755 result : {
1728 "msg": "Pulled from `<repository name>`"
1756 "msg": "Pulled from `<repository name>`"
1729 "repository": "<repository name>"
1757 "repository": "<repository name>"
1730 }
1758 }
1731 error : null
1759 error : null
1732
1760
1733 Example error output:
1761 Example error output:
1734
1762
1735 .. code-block:: bash
1763 .. code-block:: bash
1736
1764
1737 id : <id_given_in_input>
1765 id : <id_given_in_input>
1738 result : null
1766 result : null
1739 error : {
1767 error : {
1740 "Unable to pull changes from `<reponame>`"
1768 "Unable to pull changes from `<reponame>`"
1741 }
1769 }
1742
1770
1743 """
1771 """
1744
1772
1745 repo = get_repo_or_error(repoid)
1773 repo = get_repo_or_error(repoid)
1746 if not has_superadmin_permission(apiuser):
1774 if not has_superadmin_permission(apiuser):
1747 _perms = ('repository.admin',)
1775 _perms = ('repository.admin',)
1748 validate_repo_permissions(apiuser, repoid, repo, _perms)
1776 validate_repo_permissions(apiuser, repoid, repo, _perms)
1749
1777
1750 try:
1778 try:
1751 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1779 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1752 return {
1780 return {
1753 'msg': 'Pulled from `%s`' % repo.repo_name,
1781 'msg': 'Pulled from `%s`' % repo.repo_name,
1754 'repository': repo.repo_name
1782 'repository': repo.repo_name
1755 }
1783 }
1756 except Exception:
1784 except Exception:
1757 log.exception("Exception occurred while trying to "
1785 log.exception("Exception occurred while trying to "
1758 "pull changes from remote location")
1786 "pull changes from remote location")
1759 raise JSONRPCError(
1787 raise JSONRPCError(
1760 'Unable to pull changes from `%s`' % repo.repo_name
1788 'Unable to pull changes from `%s`' % repo.repo_name
1761 )
1789 )
1762
1790
1763
1791
1764 @jsonrpc_method()
1792 @jsonrpc_method()
1765 def strip(request, apiuser, repoid, revision, branch):
1793 def strip(request, apiuser, repoid, revision, branch):
1766 """
1794 """
1767 Strips the given revision from the specified repository.
1795 Strips the given revision from the specified repository.
1768
1796
1769 * This will remove the revision and all of its decendants.
1797 * This will remove the revision and all of its decendants.
1770
1798
1771 This command can only be run using an |authtoken| with admin rights to
1799 This command can only be run using an |authtoken| with admin rights to
1772 the specified repository.
1800 the specified repository.
1773
1801
1774 This command takes the following options:
1802 This command takes the following options:
1775
1803
1776 :param apiuser: This is filled automatically from the |authtoken|.
1804 :param apiuser: This is filled automatically from the |authtoken|.
1777 :type apiuser: AuthUser
1805 :type apiuser: AuthUser
1778 :param repoid: The repository name or repository ID.
1806 :param repoid: The repository name or repository ID.
1779 :type repoid: str or int
1807 :type repoid: str or int
1780 :param revision: The revision you wish to strip.
1808 :param revision: The revision you wish to strip.
1781 :type revision: str
1809 :type revision: str
1782 :param branch: The branch from which to strip the revision.
1810 :param branch: The branch from which to strip the revision.
1783 :type branch: str
1811 :type branch: str
1784
1812
1785 Example output:
1813 Example output:
1786
1814
1787 .. code-block:: bash
1815 .. code-block:: bash
1788
1816
1789 id : <id_given_in_input>
1817 id : <id_given_in_input>
1790 result : {
1818 result : {
1791 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1819 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1792 "repository": "<repository name>"
1820 "repository": "<repository name>"
1793 }
1821 }
1794 error : null
1822 error : null
1795
1823
1796 Example error output:
1824 Example error output:
1797
1825
1798 .. code-block:: bash
1826 .. code-block:: bash
1799
1827
1800 id : <id_given_in_input>
1828 id : <id_given_in_input>
1801 result : null
1829 result : null
1802 error : {
1830 error : {
1803 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1831 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1804 }
1832 }
1805
1833
1806 """
1834 """
1807
1835
1808 repo = get_repo_or_error(repoid)
1836 repo = get_repo_or_error(repoid)
1809 if not has_superadmin_permission(apiuser):
1837 if not has_superadmin_permission(apiuser):
1810 _perms = ('repository.admin',)
1838 _perms = ('repository.admin',)
1811 validate_repo_permissions(apiuser, repoid, repo, _perms)
1839 validate_repo_permissions(apiuser, repoid, repo, _perms)
1812
1840
1813 try:
1841 try:
1814 ScmModel().strip(repo, revision, branch)
1842 ScmModel().strip(repo, revision, branch)
1815 return {
1843 return {
1816 'msg': 'Stripped commit %s from repo `%s`' % (
1844 'msg': 'Stripped commit %s from repo `%s`' % (
1817 revision, repo.repo_name),
1845 revision, repo.repo_name),
1818 'repository': repo.repo_name
1846 'repository': repo.repo_name
1819 }
1847 }
1820 except Exception:
1848 except Exception:
1821 log.exception("Exception while trying to strip")
1849 log.exception("Exception while trying to strip")
1822 raise JSONRPCError(
1850 raise JSONRPCError(
1823 'Unable to strip commit %s from repo `%s`' % (
1851 'Unable to strip commit %s from repo `%s`' % (
1824 revision, repo.repo_name)
1852 revision, repo.repo_name)
1825 )
1853 )
1826
1854
1827
1855
1828 @jsonrpc_method()
1856 @jsonrpc_method()
1829 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1857 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1830 """
1858 """
1831 Returns all settings for a repository. If key is given it only returns the
1859 Returns all settings for a repository. If key is given it only returns the
1832 setting identified by the key or null.
1860 setting identified by the key or null.
1833
1861
1834 :param apiuser: This is filled automatically from the |authtoken|.
1862 :param apiuser: This is filled automatically from the |authtoken|.
1835 :type apiuser: AuthUser
1863 :type apiuser: AuthUser
1836 :param repoid: The repository name or repository id.
1864 :param repoid: The repository name or repository id.
1837 :type repoid: str or int
1865 :type repoid: str or int
1838 :param key: Key of the setting to return.
1866 :param key: Key of the setting to return.
1839 :type: key: Optional(str)
1867 :type: key: Optional(str)
1840
1868
1841 Example output:
1869 Example output:
1842
1870
1843 .. code-block:: bash
1871 .. code-block:: bash
1844
1872
1845 {
1873 {
1846 "error": null,
1874 "error": null,
1847 "id": 237,
1875 "id": 237,
1848 "result": {
1876 "result": {
1849 "extensions_largefiles": true,
1877 "extensions_largefiles": true,
1850 "hooks_changegroup_push_logger": true,
1878 "hooks_changegroup_push_logger": true,
1851 "hooks_changegroup_repo_size": false,
1879 "hooks_changegroup_repo_size": false,
1852 "hooks_outgoing_pull_logger": true,
1880 "hooks_outgoing_pull_logger": true,
1853 "phases_publish": "True",
1881 "phases_publish": "True",
1854 "rhodecode_hg_use_rebase_for_merging": true,
1882 "rhodecode_hg_use_rebase_for_merging": true,
1855 "rhodecode_pr_merge_enabled": true,
1883 "rhodecode_pr_merge_enabled": true,
1856 "rhodecode_use_outdated_comments": true
1884 "rhodecode_use_outdated_comments": true
1857 }
1885 }
1858 }
1886 }
1859 """
1887 """
1860
1888
1861 # Restrict access to this api method to admins only.
1889 # Restrict access to this api method to admins only.
1862 if not has_superadmin_permission(apiuser):
1890 if not has_superadmin_permission(apiuser):
1863 raise JSONRPCForbidden()
1891 raise JSONRPCForbidden()
1864
1892
1865 try:
1893 try:
1866 repo = get_repo_or_error(repoid)
1894 repo = get_repo_or_error(repoid)
1867 settings_model = VcsSettingsModel(repo=repo)
1895 settings_model = VcsSettingsModel(repo=repo)
1868 settings = settings_model.get_global_settings()
1896 settings = settings_model.get_global_settings()
1869 settings.update(settings_model.get_repo_settings())
1897 settings.update(settings_model.get_repo_settings())
1870
1898
1871 # If only a single setting is requested fetch it from all settings.
1899 # If only a single setting is requested fetch it from all settings.
1872 key = Optional.extract(key)
1900 key = Optional.extract(key)
1873 if key is not None:
1901 if key is not None:
1874 settings = settings.get(key, None)
1902 settings = settings.get(key, None)
1875 except Exception:
1903 except Exception:
1876 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1904 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1877 log.exception(msg)
1905 log.exception(msg)
1878 raise JSONRPCError(msg)
1906 raise JSONRPCError(msg)
1879
1907
1880 return settings
1908 return settings
1881
1909
1882
1910
1883 @jsonrpc_method()
1911 @jsonrpc_method()
1884 def set_repo_settings(request, apiuser, repoid, settings):
1912 def set_repo_settings(request, apiuser, repoid, settings):
1885 """
1913 """
1886 Update repository settings. Returns true on success.
1914 Update repository settings. Returns true on success.
1887
1915
1888 :param apiuser: This is filled automatically from the |authtoken|.
1916 :param apiuser: This is filled automatically from the |authtoken|.
1889 :type apiuser: AuthUser
1917 :type apiuser: AuthUser
1890 :param repoid: The repository name or repository id.
1918 :param repoid: The repository name or repository id.
1891 :type repoid: str or int
1919 :type repoid: str or int
1892 :param settings: The new settings for the repository.
1920 :param settings: The new settings for the repository.
1893 :type: settings: dict
1921 :type: settings: dict
1894
1922
1895 Example output:
1923 Example output:
1896
1924
1897 .. code-block:: bash
1925 .. code-block:: bash
1898
1926
1899 {
1927 {
1900 "error": null,
1928 "error": null,
1901 "id": 237,
1929 "id": 237,
1902 "result": true
1930 "result": true
1903 }
1931 }
1904 """
1932 """
1905 # Restrict access to this api method to admins only.
1933 # Restrict access to this api method to admins only.
1906 if not has_superadmin_permission(apiuser):
1934 if not has_superadmin_permission(apiuser):
1907 raise JSONRPCForbidden()
1935 raise JSONRPCForbidden()
1908
1936
1909 if type(settings) is not dict:
1937 if type(settings) is not dict:
1910 raise JSONRPCError('Settings have to be a JSON Object.')
1938 raise JSONRPCError('Settings have to be a JSON Object.')
1911
1939
1912 try:
1940 try:
1913 settings_model = VcsSettingsModel(repo=repoid)
1941 settings_model = VcsSettingsModel(repo=repoid)
1914
1942
1915 # Merge global, repo and incoming settings.
1943 # Merge global, repo and incoming settings.
1916 new_settings = settings_model.get_global_settings()
1944 new_settings = settings_model.get_global_settings()
1917 new_settings.update(settings_model.get_repo_settings())
1945 new_settings.update(settings_model.get_repo_settings())
1918 new_settings.update(settings)
1946 new_settings.update(settings)
1919
1947
1920 # Update the settings.
1948 # Update the settings.
1921 inherit_global_settings = new_settings.get(
1949 inherit_global_settings = new_settings.get(
1922 'inherit_global_settings', False)
1950 'inherit_global_settings', False)
1923 settings_model.create_or_update_repo_settings(
1951 settings_model.create_or_update_repo_settings(
1924 new_settings, inherit_global_settings=inherit_global_settings)
1952 new_settings, inherit_global_settings=inherit_global_settings)
1925 Session().commit()
1953 Session().commit()
1926 except Exception:
1954 except Exception:
1927 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1955 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1928 log.exception(msg)
1956 log.exception(msg)
1929 raise JSONRPCError(msg)
1957 raise JSONRPCError(msg)
1930
1958
1931 # Indicate success.
1959 # Indicate success.
1932 return True
1960 return True
@@ -1,1057 +1,1070 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 Repository model for rhodecode
22 Repository model for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import shutil
28 import shutil
29 import time
29 import time
30 import traceback
30 import traceback
31 from datetime import datetime
31 from datetime import datetime
32
32
33 from sqlalchemy.sql import func
33 from sqlalchemy.sql import func
34 from sqlalchemy.sql.expression import true, or_
34 from sqlalchemy.sql.expression import true, or_
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36
36
37 from rhodecode import events
37 from rhodecode import events
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 from rhodecode.lib.auth import HasUserGroupPermissionAny
40 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.lib.exceptions import AttachedForksError
41 from rhodecode.lib.exceptions import AttachedForksError
42 from rhodecode.lib.hooks_base import log_delete_repository
42 from rhodecode.lib.hooks_base import log_delete_repository
43 from rhodecode.lib.markup_renderer import MarkupRenderer
43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 from rhodecode.lib.utils import make_db_config
44 from rhodecode.lib.utils import make_db_config
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
46 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
47 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
47 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
48 from rhodecode.lib.vcs.backends import get_backend
48 from rhodecode.lib.vcs.backends import get_backend
49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
50 from rhodecode.model import BaseModel
50 from rhodecode.model import BaseModel
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
52 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
53 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
53 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
54 RepoGroup, RepositoryField)
54 RepoGroup, RepositoryField)
55 from rhodecode.model.scm import UserGroupList
55 from rhodecode.model.scm import UserGroupList
56 from rhodecode.model.settings import VcsSettingsModel
56 from rhodecode.model.settings import VcsSettingsModel
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class RepoModel(BaseModel):
62 class RepoModel(BaseModel):
63
63
64 cls = Repository
64 cls = Repository
65
65
66 def _get_user_group(self, users_group):
66 def _get_user_group(self, users_group):
67 return self._get_instance(UserGroup, users_group,
67 return self._get_instance(UserGroup, users_group,
68 callback=UserGroup.get_by_group_name)
68 callback=UserGroup.get_by_group_name)
69
69
70 def _get_repo_group(self, repo_group):
70 def _get_repo_group(self, repo_group):
71 return self._get_instance(RepoGroup, repo_group,
71 return self._get_instance(RepoGroup, repo_group,
72 callback=RepoGroup.get_by_group_name)
72 callback=RepoGroup.get_by_group_name)
73
73
74 def _create_default_perms(self, repository, private):
74 def _create_default_perms(self, repository, private):
75 # create default permission
75 # create default permission
76 default = 'repository.read'
76 default = 'repository.read'
77 def_user = User.get_default_user()
77 def_user = User.get_default_user()
78 for p in def_user.user_perms:
78 for p in def_user.user_perms:
79 if p.permission.permission_name.startswith('repository.'):
79 if p.permission.permission_name.startswith('repository.'):
80 default = p.permission.permission_name
80 default = p.permission.permission_name
81 break
81 break
82
82
83 default_perm = 'repository.none' if private else default
83 default_perm = 'repository.none' if private else default
84
84
85 repo_to_perm = UserRepoToPerm()
85 repo_to_perm = UserRepoToPerm()
86 repo_to_perm.permission = Permission.get_by_key(default_perm)
86 repo_to_perm.permission = Permission.get_by_key(default_perm)
87
87
88 repo_to_perm.repository = repository
88 repo_to_perm.repository = repository
89 repo_to_perm.user_id = def_user.user_id
89 repo_to_perm.user_id = def_user.user_id
90
90
91 return repo_to_perm
91 return repo_to_perm
92
92
93 @LazyProperty
93 @LazyProperty
94 def repos_path(self):
94 def repos_path(self):
95 """
95 """
96 Gets the repositories root path from database
96 Gets the repositories root path from database
97 """
97 """
98 settings_model = VcsSettingsModel(sa=self.sa)
98 settings_model = VcsSettingsModel(sa=self.sa)
99 return settings_model.get_repos_location()
99 return settings_model.get_repos_location()
100
100
101 def get(self, repo_id, cache=False):
101 def get(self, repo_id, cache=False):
102 repo = self.sa.query(Repository) \
102 repo = self.sa.query(Repository) \
103 .filter(Repository.repo_id == repo_id)
103 .filter(Repository.repo_id == repo_id)
104
104
105 if cache:
105 if cache:
106 repo = repo.options(FromCache("sql_cache_short",
106 repo = repo.options(FromCache("sql_cache_short",
107 "get_repo_%s" % repo_id))
107 "get_repo_%s" % repo_id))
108 return repo.scalar()
108 return repo.scalar()
109
109
110 def get_repo(self, repository):
110 def get_repo(self, repository):
111 return self._get_repo(repository)
111 return self._get_repo(repository)
112
112
113 def get_by_repo_name(self, repo_name, cache=False):
113 def get_by_repo_name(self, repo_name, cache=False):
114 repo = self.sa.query(Repository) \
114 repo = self.sa.query(Repository) \
115 .filter(Repository.repo_name == repo_name)
115 .filter(Repository.repo_name == repo_name)
116
116
117 if cache:
117 if cache:
118 repo = repo.options(FromCache("sql_cache_short",
118 repo = repo.options(FromCache("sql_cache_short",
119 "get_repo_%s" % repo_name))
119 "get_repo_%s" % repo_name))
120 return repo.scalar()
120 return repo.scalar()
121
121
122 def _extract_id_from_repo_name(self, repo_name):
122 def _extract_id_from_repo_name(self, repo_name):
123 if repo_name.startswith('/'):
123 if repo_name.startswith('/'):
124 repo_name = repo_name.lstrip('/')
124 repo_name = repo_name.lstrip('/')
125 by_id_match = re.match(r'^_(\d{1,})', repo_name)
125 by_id_match = re.match(r'^_(\d{1,})', repo_name)
126 if by_id_match:
126 if by_id_match:
127 return by_id_match.groups()[0]
127 return by_id_match.groups()[0]
128
128
129 def get_repo_by_id(self, repo_name):
129 def get_repo_by_id(self, repo_name):
130 """
130 """
131 Extracts repo_name by id from special urls.
131 Extracts repo_name by id from special urls.
132 Example url is _11/repo_name
132 Example url is _11/repo_name
133
133
134 :param repo_name:
134 :param repo_name:
135 :return: repo object if matched else None
135 :return: repo object if matched else None
136 """
136 """
137 try:
137 try:
138 _repo_id = self._extract_id_from_repo_name(repo_name)
138 _repo_id = self._extract_id_from_repo_name(repo_name)
139 if _repo_id:
139 if _repo_id:
140 return self.get(_repo_id)
140 return self.get(_repo_id)
141 except Exception:
141 except Exception:
142 log.exception('Failed to extract repo_name from URL')
142 log.exception('Failed to extract repo_name from URL')
143
143
144 return None
144 return None
145
145
146 def get_repos_for_root(self, root, traverse=False):
147 if traverse:
148 like_expression = u'{}%'.format(safe_unicode(root))
149 repos = Repository.query().filter(
150 Repository.repo_name.like(like_expression)).all()
151 else:
152 if root and not isinstance(root, RepoGroup):
153 raise ValueError(
154 'Root must be an instance '
155 'of RepoGroup, got:{} instead'.format(type(root)))
156 repos = Repository.query().filter(Repository.group == root).all()
157 return repos
158
146 def get_url(self, repo):
159 def get_url(self, repo):
147 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
160 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
148 qualified=True)
161 qualified=True)
149
162
150 def get_users(self, name_contains=None, limit=20, only_active=True):
163 def get_users(self, name_contains=None, limit=20, only_active=True):
151
164
152 # TODO: mikhail: move this method to the UserModel.
165 # TODO: mikhail: move this method to the UserModel.
153 query = self.sa.query(User)
166 query = self.sa.query(User)
154 if only_active:
167 if only_active:
155 query = query.filter(User.active == true())
168 query = query.filter(User.active == true())
156
169
157 if name_contains:
170 if name_contains:
158 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
171 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
159 query = query.filter(
172 query = query.filter(
160 or_(
173 or_(
161 User.name.ilike(ilike_expression),
174 User.name.ilike(ilike_expression),
162 User.lastname.ilike(ilike_expression),
175 User.lastname.ilike(ilike_expression),
163 User.username.ilike(ilike_expression)
176 User.username.ilike(ilike_expression)
164 )
177 )
165 )
178 )
166 query = query.limit(limit)
179 query = query.limit(limit)
167 users = query.all()
180 users = query.all()
168
181
169 _users = [
182 _users = [
170 {
183 {
171 'id': user.user_id,
184 'id': user.user_id,
172 'first_name': user.name,
185 'first_name': user.name,
173 'last_name': user.lastname,
186 'last_name': user.lastname,
174 'username': user.username,
187 'username': user.username,
175 'email': user.email,
188 'email': user.email,
176 'icon_link': h.gravatar_url(user.email, 30),
189 'icon_link': h.gravatar_url(user.email, 30),
177 'value_display': h.person(user),
190 'value_display': h.person(user),
178 'value': user.username,
191 'value': user.username,
179 'value_type': 'user',
192 'value_type': 'user',
180 'active': user.active,
193 'active': user.active,
181 }
194 }
182 for user in users
195 for user in users
183 ]
196 ]
184 return _users
197 return _users
185
198
186 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
199 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
187 # TODO: mikhail: move this method to the UserGroupModel.
200 # TODO: mikhail: move this method to the UserGroupModel.
188 query = self.sa.query(UserGroup)
201 query = self.sa.query(UserGroup)
189 if only_active:
202 if only_active:
190 query = query.filter(UserGroup.users_group_active == true())
203 query = query.filter(UserGroup.users_group_active == true())
191
204
192 if name_contains:
205 if name_contains:
193 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
206 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
194 query = query.filter(
207 query = query.filter(
195 UserGroup.users_group_name.ilike(ilike_expression))\
208 UserGroup.users_group_name.ilike(ilike_expression))\
196 .order_by(func.length(UserGroup.users_group_name))\
209 .order_by(func.length(UserGroup.users_group_name))\
197 .order_by(UserGroup.users_group_name)
210 .order_by(UserGroup.users_group_name)
198
211
199 query = query.limit(limit)
212 query = query.limit(limit)
200 user_groups = query.all()
213 user_groups = query.all()
201 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
214 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
202 user_groups = UserGroupList(user_groups, perm_set=perm_set)
215 user_groups = UserGroupList(user_groups, perm_set=perm_set)
203
216
204 _groups = [
217 _groups = [
205 {
218 {
206 'id': group.users_group_id,
219 'id': group.users_group_id,
207 # TODO: marcink figure out a way to generate the url for the
220 # TODO: marcink figure out a way to generate the url for the
208 # icon
221 # icon
209 'icon_link': '',
222 'icon_link': '',
210 'value_display': 'Group: %s (%d members)' % (
223 'value_display': 'Group: %s (%d members)' % (
211 group.users_group_name, len(group.members),),
224 group.users_group_name, len(group.members),),
212 'value': group.users_group_name,
225 'value': group.users_group_name,
213 'value_type': 'user_group',
226 'value_type': 'user_group',
214 'active': group.users_group_active,
227 'active': group.users_group_active,
215 }
228 }
216 for group in user_groups
229 for group in user_groups
217 ]
230 ]
218 return _groups
231 return _groups
219
232
220 @classmethod
233 @classmethod
221 def update_repoinfo(cls, repositories=None):
234 def update_repoinfo(cls, repositories=None):
222 if not repositories:
235 if not repositories:
223 repositories = Repository.getAll()
236 repositories = Repository.getAll()
224 for repo in repositories:
237 for repo in repositories:
225 repo.update_commit_cache()
238 repo.update_commit_cache()
226
239
227 def get_repos_as_dict(self, repo_list=None, admin=False,
240 def get_repos_as_dict(self, repo_list=None, admin=False,
228 super_user_actions=False):
241 super_user_actions=False):
229
242
230 from rhodecode.lib.utils import PartialRenderer
243 from rhodecode.lib.utils import PartialRenderer
231 _render = PartialRenderer('data_table/_dt_elements.html')
244 _render = PartialRenderer('data_table/_dt_elements.html')
232 c = _render.c
245 c = _render.c
233
246
234 def quick_menu(repo_name):
247 def quick_menu(repo_name):
235 return _render('quick_menu', repo_name)
248 return _render('quick_menu', repo_name)
236
249
237 def repo_lnk(name, rtype, rstate, private, fork_of):
250 def repo_lnk(name, rtype, rstate, private, fork_of):
238 return _render('repo_name', name, rtype, rstate, private, fork_of,
251 return _render('repo_name', name, rtype, rstate, private, fork_of,
239 short_name=not admin, admin=False)
252 short_name=not admin, admin=False)
240
253
241 def last_change(last_change):
254 def last_change(last_change):
242 return _render("last_change", last_change)
255 return _render("last_change", last_change)
243
256
244 def rss_lnk(repo_name):
257 def rss_lnk(repo_name):
245 return _render("rss", repo_name)
258 return _render("rss", repo_name)
246
259
247 def atom_lnk(repo_name):
260 def atom_lnk(repo_name):
248 return _render("atom", repo_name)
261 return _render("atom", repo_name)
249
262
250 def last_rev(repo_name, cs_cache):
263 def last_rev(repo_name, cs_cache):
251 return _render('revision', repo_name, cs_cache.get('revision'),
264 return _render('revision', repo_name, cs_cache.get('revision'),
252 cs_cache.get('raw_id'), cs_cache.get('author'),
265 cs_cache.get('raw_id'), cs_cache.get('author'),
253 cs_cache.get('message'))
266 cs_cache.get('message'))
254
267
255 def desc(desc):
268 def desc(desc):
256 if c.visual.stylify_metatags:
269 if c.visual.stylify_metatags:
257 desc = h.urlify_text(h.escaped_stylize(desc))
270 desc = h.urlify_text(h.escaped_stylize(desc))
258 else:
271 else:
259 desc = h.urlify_text(h.html_escape(desc))
272 desc = h.urlify_text(h.html_escape(desc))
260
273
261 return _render('repo_desc', desc)
274 return _render('repo_desc', desc)
262
275
263 def state(repo_state):
276 def state(repo_state):
264 return _render("repo_state", repo_state)
277 return _render("repo_state", repo_state)
265
278
266 def repo_actions(repo_name):
279 def repo_actions(repo_name):
267 return _render('repo_actions', repo_name, super_user_actions)
280 return _render('repo_actions', repo_name, super_user_actions)
268
281
269 def user_profile(username):
282 def user_profile(username):
270 return _render('user_profile', username)
283 return _render('user_profile', username)
271
284
272 repos_data = []
285 repos_data = []
273 for repo in repo_list:
286 for repo in repo_list:
274 cs_cache = repo.changeset_cache
287 cs_cache = repo.changeset_cache
275 row = {
288 row = {
276 "menu": quick_menu(repo.repo_name),
289 "menu": quick_menu(repo.repo_name),
277
290
278 "name": repo_lnk(repo.repo_name, repo.repo_type,
291 "name": repo_lnk(repo.repo_name, repo.repo_type,
279 repo.repo_state, repo.private, repo.fork),
292 repo.repo_state, repo.private, repo.fork),
280 "name_raw": repo.repo_name.lower(),
293 "name_raw": repo.repo_name.lower(),
281
294
282 "last_change": last_change(repo.last_db_change),
295 "last_change": last_change(repo.last_db_change),
283 "last_change_raw": datetime_to_time(repo.last_db_change),
296 "last_change_raw": datetime_to_time(repo.last_db_change),
284
297
285 "last_changeset": last_rev(repo.repo_name, cs_cache),
298 "last_changeset": last_rev(repo.repo_name, cs_cache),
286 "last_changeset_raw": cs_cache.get('revision'),
299 "last_changeset_raw": cs_cache.get('revision'),
287
300
288 "desc": desc(repo.description),
301 "desc": desc(repo.description),
289 "owner": user_profile(repo.user.username),
302 "owner": user_profile(repo.user.username),
290
303
291 "state": state(repo.repo_state),
304 "state": state(repo.repo_state),
292 "rss": rss_lnk(repo.repo_name),
305 "rss": rss_lnk(repo.repo_name),
293
306
294 "atom": atom_lnk(repo.repo_name),
307 "atom": atom_lnk(repo.repo_name),
295 }
308 }
296 if admin:
309 if admin:
297 row.update({
310 row.update({
298 "action": repo_actions(repo.repo_name),
311 "action": repo_actions(repo.repo_name),
299 })
312 })
300 repos_data.append(row)
313 repos_data.append(row)
301
314
302 return repos_data
315 return repos_data
303
316
304 def _get_defaults(self, repo_name):
317 def _get_defaults(self, repo_name):
305 """
318 """
306 Gets information about repository, and returns a dict for
319 Gets information about repository, and returns a dict for
307 usage in forms
320 usage in forms
308
321
309 :param repo_name:
322 :param repo_name:
310 """
323 """
311
324
312 repo_info = Repository.get_by_repo_name(repo_name)
325 repo_info = Repository.get_by_repo_name(repo_name)
313
326
314 if repo_info is None:
327 if repo_info is None:
315 return None
328 return None
316
329
317 defaults = repo_info.get_dict()
330 defaults = repo_info.get_dict()
318 defaults['repo_name'] = repo_info.just_name
331 defaults['repo_name'] = repo_info.just_name
319
332
320 groups = repo_info.groups_with_parents
333 groups = repo_info.groups_with_parents
321 parent_group = groups[-1] if groups else None
334 parent_group = groups[-1] if groups else None
322
335
323 # we use -1 as this is how in HTML, we mark an empty group
336 # we use -1 as this is how in HTML, we mark an empty group
324 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
337 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
325
338
326 keys_to_process = (
339 keys_to_process = (
327 {'k': 'repo_type', 'strip': False},
340 {'k': 'repo_type', 'strip': False},
328 {'k': 'repo_enable_downloads', 'strip': True},
341 {'k': 'repo_enable_downloads', 'strip': True},
329 {'k': 'repo_description', 'strip': True},
342 {'k': 'repo_description', 'strip': True},
330 {'k': 'repo_enable_locking', 'strip': True},
343 {'k': 'repo_enable_locking', 'strip': True},
331 {'k': 'repo_landing_rev', 'strip': True},
344 {'k': 'repo_landing_rev', 'strip': True},
332 {'k': 'clone_uri', 'strip': False},
345 {'k': 'clone_uri', 'strip': False},
333 {'k': 'repo_private', 'strip': True},
346 {'k': 'repo_private', 'strip': True},
334 {'k': 'repo_enable_statistics', 'strip': True}
347 {'k': 'repo_enable_statistics', 'strip': True}
335 )
348 )
336
349
337 for item in keys_to_process:
350 for item in keys_to_process:
338 attr = item['k']
351 attr = item['k']
339 if item['strip']:
352 if item['strip']:
340 attr = remove_prefix(item['k'], 'repo_')
353 attr = remove_prefix(item['k'], 'repo_')
341
354
342 val = defaults[attr]
355 val = defaults[attr]
343 if item['k'] == 'repo_landing_rev':
356 if item['k'] == 'repo_landing_rev':
344 val = ':'.join(defaults[attr])
357 val = ':'.join(defaults[attr])
345 defaults[item['k']] = val
358 defaults[item['k']] = val
346 if item['k'] == 'clone_uri':
359 if item['k'] == 'clone_uri':
347 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
360 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
348
361
349 # fill owner
362 # fill owner
350 if repo_info.user:
363 if repo_info.user:
351 defaults.update({'user': repo_info.user.username})
364 defaults.update({'user': repo_info.user.username})
352 else:
365 else:
353 replacement_user = User.get_first_super_admin().username
366 replacement_user = User.get_first_super_admin().username
354 defaults.update({'user': replacement_user})
367 defaults.update({'user': replacement_user})
355
368
356 # fill repository users
369 # fill repository users
357 for p in repo_info.repo_to_perm:
370 for p in repo_info.repo_to_perm:
358 defaults.update({'u_perm_%s' % p.user.user_id:
371 defaults.update({'u_perm_%s' % p.user.user_id:
359 p.permission.permission_name})
372 p.permission.permission_name})
360
373
361 # fill repository groups
374 # fill repository groups
362 for p in repo_info.users_group_to_perm:
375 for p in repo_info.users_group_to_perm:
363 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
376 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
364 p.permission.permission_name})
377 p.permission.permission_name})
365
378
366 return defaults
379 return defaults
367
380
368 def update(self, repo, **kwargs):
381 def update(self, repo, **kwargs):
369 try:
382 try:
370 cur_repo = self._get_repo(repo)
383 cur_repo = self._get_repo(repo)
371 source_repo_name = cur_repo.repo_name
384 source_repo_name = cur_repo.repo_name
372 if 'user' in kwargs:
385 if 'user' in kwargs:
373 cur_repo.user = User.get_by_username(kwargs['user'])
386 cur_repo.user = User.get_by_username(kwargs['user'])
374
387
375 if 'repo_group' in kwargs:
388 if 'repo_group' in kwargs:
376 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
389 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
377 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
390 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
378
391
379 update_keys = [
392 update_keys = [
380 (1, 'repo_description'),
393 (1, 'repo_description'),
381 (1, 'repo_landing_rev'),
394 (1, 'repo_landing_rev'),
382 (1, 'repo_private'),
395 (1, 'repo_private'),
383 (1, 'repo_enable_downloads'),
396 (1, 'repo_enable_downloads'),
384 (1, 'repo_enable_locking'),
397 (1, 'repo_enable_locking'),
385 (1, 'repo_enable_statistics'),
398 (1, 'repo_enable_statistics'),
386 (0, 'clone_uri'),
399 (0, 'clone_uri'),
387 (0, 'fork_id')
400 (0, 'fork_id')
388 ]
401 ]
389 for strip, k in update_keys:
402 for strip, k in update_keys:
390 if k in kwargs:
403 if k in kwargs:
391 val = kwargs[k]
404 val = kwargs[k]
392 if strip:
405 if strip:
393 k = remove_prefix(k, 'repo_')
406 k = remove_prefix(k, 'repo_')
394 if k == 'clone_uri':
407 if k == 'clone_uri':
395 from rhodecode.model.validators import Missing
408 from rhodecode.model.validators import Missing
396 _change = kwargs.get('clone_uri_change')
409 _change = kwargs.get('clone_uri_change')
397 if _change in [Missing, 'OLD']:
410 if _change in [Missing, 'OLD']:
398 # we don't change the value, so use original one
411 # we don't change the value, so use original one
399 val = cur_repo.clone_uri
412 val = cur_repo.clone_uri
400
413
401 setattr(cur_repo, k, val)
414 setattr(cur_repo, k, val)
402
415
403 new_name = cur_repo.get_new_name(kwargs['repo_name'])
416 new_name = cur_repo.get_new_name(kwargs['repo_name'])
404 cur_repo.repo_name = new_name
417 cur_repo.repo_name = new_name
405
418
406 # if private flag is set, reset default permission to NONE
419 # if private flag is set, reset default permission to NONE
407 if kwargs.get('repo_private'):
420 if kwargs.get('repo_private'):
408 EMPTY_PERM = 'repository.none'
421 EMPTY_PERM = 'repository.none'
409 RepoModel().grant_user_permission(
422 RepoModel().grant_user_permission(
410 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
423 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
411 )
424 )
412
425
413 # handle extra fields
426 # handle extra fields
414 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
427 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
415 kwargs):
428 kwargs):
416 k = RepositoryField.un_prefix_key(field)
429 k = RepositoryField.un_prefix_key(field)
417 ex_field = RepositoryField.get_by_key_name(
430 ex_field = RepositoryField.get_by_key_name(
418 key=k, repo=cur_repo)
431 key=k, repo=cur_repo)
419 if ex_field:
432 if ex_field:
420 ex_field.field_value = kwargs[field]
433 ex_field.field_value = kwargs[field]
421 self.sa.add(ex_field)
434 self.sa.add(ex_field)
422 self.sa.add(cur_repo)
435 self.sa.add(cur_repo)
423
436
424 if source_repo_name != new_name:
437 if source_repo_name != new_name:
425 # rename repository
438 # rename repository
426 self._rename_filesystem_repo(
439 self._rename_filesystem_repo(
427 old=source_repo_name, new=new_name)
440 old=source_repo_name, new=new_name)
428
441
429 return cur_repo
442 return cur_repo
430 except Exception:
443 except Exception:
431 log.error(traceback.format_exc())
444 log.error(traceback.format_exc())
432 raise
445 raise
433
446
434 def _create_repo(self, repo_name, repo_type, description, owner,
447 def _create_repo(self, repo_name, repo_type, description, owner,
435 private=False, clone_uri=None, repo_group=None,
448 private=False, clone_uri=None, repo_group=None,
436 landing_rev='rev:tip', fork_of=None,
449 landing_rev='rev:tip', fork_of=None,
437 copy_fork_permissions=False, enable_statistics=False,
450 copy_fork_permissions=False, enable_statistics=False,
438 enable_locking=False, enable_downloads=False,
451 enable_locking=False, enable_downloads=False,
439 copy_group_permissions=False,
452 copy_group_permissions=False,
440 state=Repository.STATE_PENDING):
453 state=Repository.STATE_PENDING):
441 """
454 """
442 Create repository inside database with PENDING state, this should be
455 Create repository inside database with PENDING state, this should be
443 only executed by create() repo. With exception of importing existing
456 only executed by create() repo. With exception of importing existing
444 repos
457 repos
445 """
458 """
446 from rhodecode.model.scm import ScmModel
459 from rhodecode.model.scm import ScmModel
447
460
448 owner = self._get_user(owner)
461 owner = self._get_user(owner)
449 fork_of = self._get_repo(fork_of)
462 fork_of = self._get_repo(fork_of)
450 repo_group = self._get_repo_group(safe_int(repo_group))
463 repo_group = self._get_repo_group(safe_int(repo_group))
451
464
452 try:
465 try:
453 repo_name = safe_unicode(repo_name)
466 repo_name = safe_unicode(repo_name)
454 description = safe_unicode(description)
467 description = safe_unicode(description)
455 # repo name is just a name of repository
468 # repo name is just a name of repository
456 # while repo_name_full is a full qualified name that is combined
469 # while repo_name_full is a full qualified name that is combined
457 # with name and path of group
470 # with name and path of group
458 repo_name_full = repo_name
471 repo_name_full = repo_name
459 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
472 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
460
473
461 new_repo = Repository()
474 new_repo = Repository()
462 new_repo.repo_state = state
475 new_repo.repo_state = state
463 new_repo.enable_statistics = False
476 new_repo.enable_statistics = False
464 new_repo.repo_name = repo_name_full
477 new_repo.repo_name = repo_name_full
465 new_repo.repo_type = repo_type
478 new_repo.repo_type = repo_type
466 new_repo.user = owner
479 new_repo.user = owner
467 new_repo.group = repo_group
480 new_repo.group = repo_group
468 new_repo.description = description or repo_name
481 new_repo.description = description or repo_name
469 new_repo.private = private
482 new_repo.private = private
470 new_repo.clone_uri = clone_uri
483 new_repo.clone_uri = clone_uri
471 new_repo.landing_rev = landing_rev
484 new_repo.landing_rev = landing_rev
472
485
473 new_repo.enable_statistics = enable_statistics
486 new_repo.enable_statistics = enable_statistics
474 new_repo.enable_locking = enable_locking
487 new_repo.enable_locking = enable_locking
475 new_repo.enable_downloads = enable_downloads
488 new_repo.enable_downloads = enable_downloads
476
489
477 if repo_group:
490 if repo_group:
478 new_repo.enable_locking = repo_group.enable_locking
491 new_repo.enable_locking = repo_group.enable_locking
479
492
480 if fork_of:
493 if fork_of:
481 parent_repo = fork_of
494 parent_repo = fork_of
482 new_repo.fork = parent_repo
495 new_repo.fork = parent_repo
483
496
484 events.trigger(events.RepoPreCreateEvent(new_repo))
497 events.trigger(events.RepoPreCreateEvent(new_repo))
485
498
486 self.sa.add(new_repo)
499 self.sa.add(new_repo)
487
500
488 EMPTY_PERM = 'repository.none'
501 EMPTY_PERM = 'repository.none'
489 if fork_of and copy_fork_permissions:
502 if fork_of and copy_fork_permissions:
490 repo = fork_of
503 repo = fork_of
491 user_perms = UserRepoToPerm.query() \
504 user_perms = UserRepoToPerm.query() \
492 .filter(UserRepoToPerm.repository == repo).all()
505 .filter(UserRepoToPerm.repository == repo).all()
493 group_perms = UserGroupRepoToPerm.query() \
506 group_perms = UserGroupRepoToPerm.query() \
494 .filter(UserGroupRepoToPerm.repository == repo).all()
507 .filter(UserGroupRepoToPerm.repository == repo).all()
495
508
496 for perm in user_perms:
509 for perm in user_perms:
497 UserRepoToPerm.create(
510 UserRepoToPerm.create(
498 perm.user, new_repo, perm.permission)
511 perm.user, new_repo, perm.permission)
499
512
500 for perm in group_perms:
513 for perm in group_perms:
501 UserGroupRepoToPerm.create(
514 UserGroupRepoToPerm.create(
502 perm.users_group, new_repo, perm.permission)
515 perm.users_group, new_repo, perm.permission)
503 # in case we copy permissions and also set this repo to private
516 # in case we copy permissions and also set this repo to private
504 # override the default user permission to make it a private
517 # override the default user permission to make it a private
505 # repo
518 # repo
506 if private:
519 if private:
507 RepoModel(self.sa).grant_user_permission(
520 RepoModel(self.sa).grant_user_permission(
508 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
521 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
509
522
510 elif repo_group and copy_group_permissions:
523 elif repo_group and copy_group_permissions:
511 user_perms = UserRepoGroupToPerm.query() \
524 user_perms = UserRepoGroupToPerm.query() \
512 .filter(UserRepoGroupToPerm.group == repo_group).all()
525 .filter(UserRepoGroupToPerm.group == repo_group).all()
513
526
514 group_perms = UserGroupRepoGroupToPerm.query() \
527 group_perms = UserGroupRepoGroupToPerm.query() \
515 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
528 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
516
529
517 for perm in user_perms:
530 for perm in user_perms:
518 perm_name = perm.permission.permission_name.replace(
531 perm_name = perm.permission.permission_name.replace(
519 'group.', 'repository.')
532 'group.', 'repository.')
520 perm_obj = Permission.get_by_key(perm_name)
533 perm_obj = Permission.get_by_key(perm_name)
521 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
534 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
522
535
523 for perm in group_perms:
536 for perm in group_perms:
524 perm_name = perm.permission.permission_name.replace(
537 perm_name = perm.permission.permission_name.replace(
525 'group.', 'repository.')
538 'group.', 'repository.')
526 perm_obj = Permission.get_by_key(perm_name)
539 perm_obj = Permission.get_by_key(perm_name)
527 UserGroupRepoToPerm.create(
540 UserGroupRepoToPerm.create(
528 perm.users_group, new_repo, perm_obj)
541 perm.users_group, new_repo, perm_obj)
529
542
530 if private:
543 if private:
531 RepoModel(self.sa).grant_user_permission(
544 RepoModel(self.sa).grant_user_permission(
532 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
545 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
533
546
534 else:
547 else:
535 perm_obj = self._create_default_perms(new_repo, private)
548 perm_obj = self._create_default_perms(new_repo, private)
536 self.sa.add(perm_obj)
549 self.sa.add(perm_obj)
537
550
538 # now automatically start following this repository as owner
551 # now automatically start following this repository as owner
539 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
552 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
540 owner.user_id)
553 owner.user_id)
541
554
542 # we need to flush here, in order to check if database won't
555 # we need to flush here, in order to check if database won't
543 # throw any exceptions, create filesystem dirs at the very end
556 # throw any exceptions, create filesystem dirs at the very end
544 self.sa.flush()
557 self.sa.flush()
545 events.trigger(events.RepoCreateEvent(new_repo))
558 events.trigger(events.RepoCreateEvent(new_repo))
546 return new_repo
559 return new_repo
547
560
548 except Exception:
561 except Exception:
549 log.error(traceback.format_exc())
562 log.error(traceback.format_exc())
550 raise
563 raise
551
564
552 def create(self, form_data, cur_user):
565 def create(self, form_data, cur_user):
553 """
566 """
554 Create repository using celery tasks
567 Create repository using celery tasks
555
568
556 :param form_data:
569 :param form_data:
557 :param cur_user:
570 :param cur_user:
558 """
571 """
559 from rhodecode.lib.celerylib import tasks, run_task
572 from rhodecode.lib.celerylib import tasks, run_task
560 return run_task(tasks.create_repo, form_data, cur_user)
573 return run_task(tasks.create_repo, form_data, cur_user)
561
574
562 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
575 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
563 perm_deletions=None, check_perms=True,
576 perm_deletions=None, check_perms=True,
564 cur_user=None):
577 cur_user=None):
565 if not perm_additions:
578 if not perm_additions:
566 perm_additions = []
579 perm_additions = []
567 if not perm_updates:
580 if not perm_updates:
568 perm_updates = []
581 perm_updates = []
569 if not perm_deletions:
582 if not perm_deletions:
570 perm_deletions = []
583 perm_deletions = []
571
584
572 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
585 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
573
586
574 # update permissions
587 # update permissions
575 for member_id, perm, member_type in perm_updates:
588 for member_id, perm, member_type in perm_updates:
576 member_id = int(member_id)
589 member_id = int(member_id)
577 if member_type == 'user':
590 if member_type == 'user':
578 # this updates also current one if found
591 # this updates also current one if found
579 self.grant_user_permission(
592 self.grant_user_permission(
580 repo=repo, user=member_id, perm=perm)
593 repo=repo, user=member_id, perm=perm)
581 else: # set for user group
594 else: # set for user group
582 # check if we have permissions to alter this usergroup
595 # check if we have permissions to alter this usergroup
583 member_name = UserGroup.get(member_id).users_group_name
596 member_name = UserGroup.get(member_id).users_group_name
584 if not check_perms or HasUserGroupPermissionAny(
597 if not check_perms or HasUserGroupPermissionAny(
585 *req_perms)(member_name, user=cur_user):
598 *req_perms)(member_name, user=cur_user):
586 self.grant_user_group_permission(
599 self.grant_user_group_permission(
587 repo=repo, group_name=member_id, perm=perm)
600 repo=repo, group_name=member_id, perm=perm)
588
601
589 # set new permissions
602 # set new permissions
590 for member_id, perm, member_type in perm_additions:
603 for member_id, perm, member_type in perm_additions:
591 member_id = int(member_id)
604 member_id = int(member_id)
592 if member_type == 'user':
605 if member_type == 'user':
593 self.grant_user_permission(
606 self.grant_user_permission(
594 repo=repo, user=member_id, perm=perm)
607 repo=repo, user=member_id, perm=perm)
595 else: # set for user group
608 else: # set for user group
596 # check if we have permissions to alter this usergroup
609 # check if we have permissions to alter this usergroup
597 member_name = UserGroup.get(member_id).users_group_name
610 member_name = UserGroup.get(member_id).users_group_name
598 if not check_perms or HasUserGroupPermissionAny(
611 if not check_perms or HasUserGroupPermissionAny(
599 *req_perms)(member_name, user=cur_user):
612 *req_perms)(member_name, user=cur_user):
600 self.grant_user_group_permission(
613 self.grant_user_group_permission(
601 repo=repo, group_name=member_id, perm=perm)
614 repo=repo, group_name=member_id, perm=perm)
602
615
603 # delete permissions
616 # delete permissions
604 for member_id, perm, member_type in perm_deletions:
617 for member_id, perm, member_type in perm_deletions:
605 member_id = int(member_id)
618 member_id = int(member_id)
606 if member_type == 'user':
619 if member_type == 'user':
607 self.revoke_user_permission(repo=repo, user=member_id)
620 self.revoke_user_permission(repo=repo, user=member_id)
608 else: # set for user group
621 else: # set for user group
609 # check if we have permissions to alter this usergroup
622 # check if we have permissions to alter this usergroup
610 member_name = UserGroup.get(member_id).users_group_name
623 member_name = UserGroup.get(member_id).users_group_name
611 if not check_perms or HasUserGroupPermissionAny(
624 if not check_perms or HasUserGroupPermissionAny(
612 *req_perms)(member_name, user=cur_user):
625 *req_perms)(member_name, user=cur_user):
613 self.revoke_user_group_permission(
626 self.revoke_user_group_permission(
614 repo=repo, group_name=member_id)
627 repo=repo, group_name=member_id)
615
628
616 def create_fork(self, form_data, cur_user):
629 def create_fork(self, form_data, cur_user):
617 """
630 """
618 Simple wrapper into executing celery task for fork creation
631 Simple wrapper into executing celery task for fork creation
619
632
620 :param form_data:
633 :param form_data:
621 :param cur_user:
634 :param cur_user:
622 """
635 """
623 from rhodecode.lib.celerylib import tasks, run_task
636 from rhodecode.lib.celerylib import tasks, run_task
624 return run_task(tasks.create_repo_fork, form_data, cur_user)
637 return run_task(tasks.create_repo_fork, form_data, cur_user)
625
638
626 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
639 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
627 """
640 """
628 Delete given repository, forks parameter defines what do do with
641 Delete given repository, forks parameter defines what do do with
629 attached forks. Throws AttachedForksError if deleted repo has attached
642 attached forks. Throws AttachedForksError if deleted repo has attached
630 forks
643 forks
631
644
632 :param repo:
645 :param repo:
633 :param forks: str 'delete' or 'detach'
646 :param forks: str 'delete' or 'detach'
634 :param fs_remove: remove(archive) repo from filesystem
647 :param fs_remove: remove(archive) repo from filesystem
635 """
648 """
636 if not cur_user:
649 if not cur_user:
637 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
650 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
638 repo = self._get_repo(repo)
651 repo = self._get_repo(repo)
639 if repo:
652 if repo:
640 if forks == 'detach':
653 if forks == 'detach':
641 for r in repo.forks:
654 for r in repo.forks:
642 r.fork = None
655 r.fork = None
643 self.sa.add(r)
656 self.sa.add(r)
644 elif forks == 'delete':
657 elif forks == 'delete':
645 for r in repo.forks:
658 for r in repo.forks:
646 self.delete(r, forks='delete')
659 self.delete(r, forks='delete')
647 elif [f for f in repo.forks]:
660 elif [f for f in repo.forks]:
648 raise AttachedForksError()
661 raise AttachedForksError()
649
662
650 old_repo_dict = repo.get_dict()
663 old_repo_dict = repo.get_dict()
651 events.trigger(events.RepoPreDeleteEvent(repo))
664 events.trigger(events.RepoPreDeleteEvent(repo))
652 try:
665 try:
653 self.sa.delete(repo)
666 self.sa.delete(repo)
654 if fs_remove:
667 if fs_remove:
655 self._delete_filesystem_repo(repo)
668 self._delete_filesystem_repo(repo)
656 else:
669 else:
657 log.debug('skipping removal from filesystem')
670 log.debug('skipping removal from filesystem')
658 old_repo_dict.update({
671 old_repo_dict.update({
659 'deleted_by': cur_user,
672 'deleted_by': cur_user,
660 'deleted_on': time.time(),
673 'deleted_on': time.time(),
661 })
674 })
662 log_delete_repository(**old_repo_dict)
675 log_delete_repository(**old_repo_dict)
663 events.trigger(events.RepoDeleteEvent(repo))
676 events.trigger(events.RepoDeleteEvent(repo))
664 except Exception:
677 except Exception:
665 log.error(traceback.format_exc())
678 log.error(traceback.format_exc())
666 raise
679 raise
667
680
668 def grant_user_permission(self, repo, user, perm):
681 def grant_user_permission(self, repo, user, perm):
669 """
682 """
670 Grant permission for user on given repository, or update existing one
683 Grant permission for user on given repository, or update existing one
671 if found
684 if found
672
685
673 :param repo: Instance of Repository, repository_id, or repository name
686 :param repo: Instance of Repository, repository_id, or repository name
674 :param user: Instance of User, user_id or username
687 :param user: Instance of User, user_id or username
675 :param perm: Instance of Permission, or permission_name
688 :param perm: Instance of Permission, or permission_name
676 """
689 """
677 user = self._get_user(user)
690 user = self._get_user(user)
678 repo = self._get_repo(repo)
691 repo = self._get_repo(repo)
679 permission = self._get_perm(perm)
692 permission = self._get_perm(perm)
680
693
681 # check if we have that permission already
694 # check if we have that permission already
682 obj = self.sa.query(UserRepoToPerm) \
695 obj = self.sa.query(UserRepoToPerm) \
683 .filter(UserRepoToPerm.user == user) \
696 .filter(UserRepoToPerm.user == user) \
684 .filter(UserRepoToPerm.repository == repo) \
697 .filter(UserRepoToPerm.repository == repo) \
685 .scalar()
698 .scalar()
686 if obj is None:
699 if obj is None:
687 # create new !
700 # create new !
688 obj = UserRepoToPerm()
701 obj = UserRepoToPerm()
689 obj.repository = repo
702 obj.repository = repo
690 obj.user = user
703 obj.user = user
691 obj.permission = permission
704 obj.permission = permission
692 self.sa.add(obj)
705 self.sa.add(obj)
693 log.debug('Granted perm %s to %s on %s', perm, user, repo)
706 log.debug('Granted perm %s to %s on %s', perm, user, repo)
694 action_logger_generic(
707 action_logger_generic(
695 'granted permission: {} to user: {} on repo: {}'.format(
708 'granted permission: {} to user: {} on repo: {}'.format(
696 perm, user, repo), namespace='security.repo')
709 perm, user, repo), namespace='security.repo')
697 return obj
710 return obj
698
711
699 def revoke_user_permission(self, repo, user):
712 def revoke_user_permission(self, repo, user):
700 """
713 """
701 Revoke permission for user on given repository
714 Revoke permission for user on given repository
702
715
703 :param repo: Instance of Repository, repository_id, or repository name
716 :param repo: Instance of Repository, repository_id, or repository name
704 :param user: Instance of User, user_id or username
717 :param user: Instance of User, user_id or username
705 """
718 """
706
719
707 user = self._get_user(user)
720 user = self._get_user(user)
708 repo = self._get_repo(repo)
721 repo = self._get_repo(repo)
709
722
710 obj = self.sa.query(UserRepoToPerm) \
723 obj = self.sa.query(UserRepoToPerm) \
711 .filter(UserRepoToPerm.repository == repo) \
724 .filter(UserRepoToPerm.repository == repo) \
712 .filter(UserRepoToPerm.user == user) \
725 .filter(UserRepoToPerm.user == user) \
713 .scalar()
726 .scalar()
714 if obj:
727 if obj:
715 self.sa.delete(obj)
728 self.sa.delete(obj)
716 log.debug('Revoked perm on %s on %s', repo, user)
729 log.debug('Revoked perm on %s on %s', repo, user)
717 action_logger_generic(
730 action_logger_generic(
718 'revoked permission from user: {} on repo: {}'.format(
731 'revoked permission from user: {} on repo: {}'.format(
719 user, repo), namespace='security.repo')
732 user, repo), namespace='security.repo')
720
733
721 def grant_user_group_permission(self, repo, group_name, perm):
734 def grant_user_group_permission(self, repo, group_name, perm):
722 """
735 """
723 Grant permission for user group on given repository, or update
736 Grant permission for user group on given repository, or update
724 existing one if found
737 existing one if found
725
738
726 :param repo: Instance of Repository, repository_id, or repository name
739 :param repo: Instance of Repository, repository_id, or repository name
727 :param group_name: Instance of UserGroup, users_group_id,
740 :param group_name: Instance of UserGroup, users_group_id,
728 or user group name
741 or user group name
729 :param perm: Instance of Permission, or permission_name
742 :param perm: Instance of Permission, or permission_name
730 """
743 """
731 repo = self._get_repo(repo)
744 repo = self._get_repo(repo)
732 group_name = self._get_user_group(group_name)
745 group_name = self._get_user_group(group_name)
733 permission = self._get_perm(perm)
746 permission = self._get_perm(perm)
734
747
735 # check if we have that permission already
748 # check if we have that permission already
736 obj = self.sa.query(UserGroupRepoToPerm) \
749 obj = self.sa.query(UserGroupRepoToPerm) \
737 .filter(UserGroupRepoToPerm.users_group == group_name) \
750 .filter(UserGroupRepoToPerm.users_group == group_name) \
738 .filter(UserGroupRepoToPerm.repository == repo) \
751 .filter(UserGroupRepoToPerm.repository == repo) \
739 .scalar()
752 .scalar()
740
753
741 if obj is None:
754 if obj is None:
742 # create new
755 # create new
743 obj = UserGroupRepoToPerm()
756 obj = UserGroupRepoToPerm()
744
757
745 obj.repository = repo
758 obj.repository = repo
746 obj.users_group = group_name
759 obj.users_group = group_name
747 obj.permission = permission
760 obj.permission = permission
748 self.sa.add(obj)
761 self.sa.add(obj)
749 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
762 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
750 action_logger_generic(
763 action_logger_generic(
751 'granted permission: {} to usergroup: {} on repo: {}'.format(
764 'granted permission: {} to usergroup: {} on repo: {}'.format(
752 perm, group_name, repo), namespace='security.repo')
765 perm, group_name, repo), namespace='security.repo')
753
766
754 return obj
767 return obj
755
768
756 def revoke_user_group_permission(self, repo, group_name):
769 def revoke_user_group_permission(self, repo, group_name):
757 """
770 """
758 Revoke permission for user group on given repository
771 Revoke permission for user group on given repository
759
772
760 :param repo: Instance of Repository, repository_id, or repository name
773 :param repo: Instance of Repository, repository_id, or repository name
761 :param group_name: Instance of UserGroup, users_group_id,
774 :param group_name: Instance of UserGroup, users_group_id,
762 or user group name
775 or user group name
763 """
776 """
764 repo = self._get_repo(repo)
777 repo = self._get_repo(repo)
765 group_name = self._get_user_group(group_name)
778 group_name = self._get_user_group(group_name)
766
779
767 obj = self.sa.query(UserGroupRepoToPerm) \
780 obj = self.sa.query(UserGroupRepoToPerm) \
768 .filter(UserGroupRepoToPerm.repository == repo) \
781 .filter(UserGroupRepoToPerm.repository == repo) \
769 .filter(UserGroupRepoToPerm.users_group == group_name) \
782 .filter(UserGroupRepoToPerm.users_group == group_name) \
770 .scalar()
783 .scalar()
771 if obj:
784 if obj:
772 self.sa.delete(obj)
785 self.sa.delete(obj)
773 log.debug('Revoked perm to %s on %s', repo, group_name)
786 log.debug('Revoked perm to %s on %s', repo, group_name)
774 action_logger_generic(
787 action_logger_generic(
775 'revoked permission from usergroup: {} on repo: {}'.format(
788 'revoked permission from usergroup: {} on repo: {}'.format(
776 group_name, repo), namespace='security.repo')
789 group_name, repo), namespace='security.repo')
777
790
778 def delete_stats(self, repo_name):
791 def delete_stats(self, repo_name):
779 """
792 """
780 removes stats for given repo
793 removes stats for given repo
781
794
782 :param repo_name:
795 :param repo_name:
783 """
796 """
784 repo = self._get_repo(repo_name)
797 repo = self._get_repo(repo_name)
785 try:
798 try:
786 obj = self.sa.query(Statistics) \
799 obj = self.sa.query(Statistics) \
787 .filter(Statistics.repository == repo).scalar()
800 .filter(Statistics.repository == repo).scalar()
788 if obj:
801 if obj:
789 self.sa.delete(obj)
802 self.sa.delete(obj)
790 except Exception:
803 except Exception:
791 log.error(traceback.format_exc())
804 log.error(traceback.format_exc())
792 raise
805 raise
793
806
794 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
807 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
795 field_type='str', field_desc=''):
808 field_type='str', field_desc=''):
796
809
797 repo = self._get_repo(repo_name)
810 repo = self._get_repo(repo_name)
798
811
799 new_field = RepositoryField()
812 new_field = RepositoryField()
800 new_field.repository = repo
813 new_field.repository = repo
801 new_field.field_key = field_key
814 new_field.field_key = field_key
802 new_field.field_type = field_type # python type
815 new_field.field_type = field_type # python type
803 new_field.field_value = field_value
816 new_field.field_value = field_value
804 new_field.field_desc = field_desc
817 new_field.field_desc = field_desc
805 new_field.field_label = field_label
818 new_field.field_label = field_label
806 self.sa.add(new_field)
819 self.sa.add(new_field)
807 return new_field
820 return new_field
808
821
809 def delete_repo_field(self, repo_name, field_key):
822 def delete_repo_field(self, repo_name, field_key):
810 repo = self._get_repo(repo_name)
823 repo = self._get_repo(repo_name)
811 field = RepositoryField.get_by_key_name(field_key, repo)
824 field = RepositoryField.get_by_key_name(field_key, repo)
812 if field:
825 if field:
813 self.sa.delete(field)
826 self.sa.delete(field)
814
827
815 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
828 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
816 clone_uri=None, repo_store_location=None,
829 clone_uri=None, repo_store_location=None,
817 use_global_config=False):
830 use_global_config=False):
818 """
831 """
819 makes repository on filesystem. It's group aware means it'll create
832 makes repository on filesystem. It's group aware means it'll create
820 a repository within a group, and alter the paths accordingly of
833 a repository within a group, and alter the paths accordingly of
821 group location
834 group location
822
835
823 :param repo_name:
836 :param repo_name:
824 :param alias:
837 :param alias:
825 :param parent:
838 :param parent:
826 :param clone_uri:
839 :param clone_uri:
827 :param repo_store_location:
840 :param repo_store_location:
828 """
841 """
829 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
842 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
830 from rhodecode.model.scm import ScmModel
843 from rhodecode.model.scm import ScmModel
831
844
832 if Repository.NAME_SEP in repo_name:
845 if Repository.NAME_SEP in repo_name:
833 raise ValueError(
846 raise ValueError(
834 'repo_name must not contain groups got `%s`' % repo_name)
847 'repo_name must not contain groups got `%s`' % repo_name)
835
848
836 if isinstance(repo_group, RepoGroup):
849 if isinstance(repo_group, RepoGroup):
837 new_parent_path = os.sep.join(repo_group.full_path_splitted)
850 new_parent_path = os.sep.join(repo_group.full_path_splitted)
838 else:
851 else:
839 new_parent_path = repo_group or ''
852 new_parent_path = repo_group or ''
840
853
841 if repo_store_location:
854 if repo_store_location:
842 _paths = [repo_store_location]
855 _paths = [repo_store_location]
843 else:
856 else:
844 _paths = [self.repos_path, new_parent_path, repo_name]
857 _paths = [self.repos_path, new_parent_path, repo_name]
845 # we need to make it str for mercurial
858 # we need to make it str for mercurial
846 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
859 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
847
860
848 # check if this path is not a repository
861 # check if this path is not a repository
849 if is_valid_repo(repo_path, self.repos_path):
862 if is_valid_repo(repo_path, self.repos_path):
850 raise Exception('This path %s is a valid repository' % repo_path)
863 raise Exception('This path %s is a valid repository' % repo_path)
851
864
852 # check if this path is a group
865 # check if this path is a group
853 if is_valid_repo_group(repo_path, self.repos_path):
866 if is_valid_repo_group(repo_path, self.repos_path):
854 raise Exception('This path %s is a valid group' % repo_path)
867 raise Exception('This path %s is a valid group' % repo_path)
855
868
856 log.info('creating repo %s in %s from url: `%s`',
869 log.info('creating repo %s in %s from url: `%s`',
857 repo_name, safe_unicode(repo_path),
870 repo_name, safe_unicode(repo_path),
858 obfuscate_url_pw(clone_uri))
871 obfuscate_url_pw(clone_uri))
859
872
860 backend = get_backend(repo_type)
873 backend = get_backend(repo_type)
861
874
862 config_repo = None if use_global_config else repo_name
875 config_repo = None if use_global_config else repo_name
863 if config_repo and new_parent_path:
876 if config_repo and new_parent_path:
864 config_repo = Repository.NAME_SEP.join(
877 config_repo = Repository.NAME_SEP.join(
865 (new_parent_path, config_repo))
878 (new_parent_path, config_repo))
866 config = make_db_config(clear_session=False, repo=config_repo)
879 config = make_db_config(clear_session=False, repo=config_repo)
867 config.set('extensions', 'largefiles', '')
880 config.set('extensions', 'largefiles', '')
868
881
869 # patch and reset hooks section of UI config to not run any
882 # patch and reset hooks section of UI config to not run any
870 # hooks on creating remote repo
883 # hooks on creating remote repo
871 config.clear_section('hooks')
884 config.clear_section('hooks')
872
885
873 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
886 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
874 if repo_type == 'git':
887 if repo_type == 'git':
875 repo = backend(
888 repo = backend(
876 repo_path, config=config, create=True, src_url=clone_uri,
889 repo_path, config=config, create=True, src_url=clone_uri,
877 bare=True)
890 bare=True)
878 else:
891 else:
879 repo = backend(
892 repo = backend(
880 repo_path, config=config, create=True, src_url=clone_uri)
893 repo_path, config=config, create=True, src_url=clone_uri)
881
894
882 ScmModel().install_hooks(repo, repo_type=repo_type)
895 ScmModel().install_hooks(repo, repo_type=repo_type)
883
896
884 log.debug('Created repo %s with %s backend',
897 log.debug('Created repo %s with %s backend',
885 safe_unicode(repo_name), safe_unicode(repo_type))
898 safe_unicode(repo_name), safe_unicode(repo_type))
886 return repo
899 return repo
887
900
888 def _rename_filesystem_repo(self, old, new):
901 def _rename_filesystem_repo(self, old, new):
889 """
902 """
890 renames repository on filesystem
903 renames repository on filesystem
891
904
892 :param old: old name
905 :param old: old name
893 :param new: new name
906 :param new: new name
894 """
907 """
895 log.info('renaming repo from %s to %s', old, new)
908 log.info('renaming repo from %s to %s', old, new)
896
909
897 old_path = os.path.join(self.repos_path, old)
910 old_path = os.path.join(self.repos_path, old)
898 new_path = os.path.join(self.repos_path, new)
911 new_path = os.path.join(self.repos_path, new)
899 if os.path.isdir(new_path):
912 if os.path.isdir(new_path):
900 raise Exception(
913 raise Exception(
901 'Was trying to rename to already existing dir %s' % new_path
914 'Was trying to rename to already existing dir %s' % new_path
902 )
915 )
903 shutil.move(old_path, new_path)
916 shutil.move(old_path, new_path)
904
917
905 def _delete_filesystem_repo(self, repo):
918 def _delete_filesystem_repo(self, repo):
906 """
919 """
907 removes repo from filesystem, the removal is acctually made by
920 removes repo from filesystem, the removal is acctually made by
908 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
921 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
909 repository is no longer valid for rhodecode, can be undeleted later on
922 repository is no longer valid for rhodecode, can be undeleted later on
910 by reverting the renames on this repository
923 by reverting the renames on this repository
911
924
912 :param repo: repo object
925 :param repo: repo object
913 """
926 """
914 rm_path = os.path.join(self.repos_path, repo.repo_name)
927 rm_path = os.path.join(self.repos_path, repo.repo_name)
915 repo_group = repo.group
928 repo_group = repo.group
916 log.info("Removing repository %s", rm_path)
929 log.info("Removing repository %s", rm_path)
917 # disable hg/git internal that it doesn't get detected as repo
930 # disable hg/git internal that it doesn't get detected as repo
918 alias = repo.repo_type
931 alias = repo.repo_type
919
932
920 config = make_db_config(clear_session=False)
933 config = make_db_config(clear_session=False)
921 config.set('extensions', 'largefiles', '')
934 config.set('extensions', 'largefiles', '')
922 bare = getattr(repo.scm_instance(config=config), 'bare', False)
935 bare = getattr(repo.scm_instance(config=config), 'bare', False)
923
936
924 # skip this for bare git repos
937 # skip this for bare git repos
925 if not bare:
938 if not bare:
926 # disable VCS repo
939 # disable VCS repo
927 vcs_path = os.path.join(rm_path, '.%s' % alias)
940 vcs_path = os.path.join(rm_path, '.%s' % alias)
928 if os.path.exists(vcs_path):
941 if os.path.exists(vcs_path):
929 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
942 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
930
943
931 _now = datetime.now()
944 _now = datetime.now()
932 _ms = str(_now.microsecond).rjust(6, '0')
945 _ms = str(_now.microsecond).rjust(6, '0')
933 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
946 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
934 repo.just_name)
947 repo.just_name)
935 if repo_group:
948 if repo_group:
936 # if repository is in group, prefix the removal path with the group
949 # if repository is in group, prefix the removal path with the group
937 args = repo_group.full_path_splitted + [_d]
950 args = repo_group.full_path_splitted + [_d]
938 _d = os.path.join(*args)
951 _d = os.path.join(*args)
939
952
940 if os.path.isdir(rm_path):
953 if os.path.isdir(rm_path):
941 shutil.move(rm_path, os.path.join(self.repos_path, _d))
954 shutil.move(rm_path, os.path.join(self.repos_path, _d))
942
955
943
956
944 class ReadmeFinder:
957 class ReadmeFinder:
945 """
958 """
946 Utility which knows how to find a readme for a specific commit.
959 Utility which knows how to find a readme for a specific commit.
947
960
948 The main idea is that this is a configurable algorithm. When creating an
961 The main idea is that this is a configurable algorithm. When creating an
949 instance you can define parameters, currently only the `default_renderer`.
962 instance you can define parameters, currently only the `default_renderer`.
950 Based on this configuration the method :meth:`search` behaves slightly
963 Based on this configuration the method :meth:`search` behaves slightly
951 different.
964 different.
952 """
965 """
953
966
954 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
967 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
955 path_re = re.compile(r'^docs?', re.IGNORECASE)
968 path_re = re.compile(r'^docs?', re.IGNORECASE)
956
969
957 default_priorities = {
970 default_priorities = {
958 None: 0,
971 None: 0,
959 '.text': 2,
972 '.text': 2,
960 '.txt': 3,
973 '.txt': 3,
961 '.rst': 1,
974 '.rst': 1,
962 '.rest': 2,
975 '.rest': 2,
963 '.md': 1,
976 '.md': 1,
964 '.mkdn': 2,
977 '.mkdn': 2,
965 '.mdown': 3,
978 '.mdown': 3,
966 '.markdown': 4,
979 '.markdown': 4,
967 }
980 }
968
981
969 path_priority = {
982 path_priority = {
970 'doc': 0,
983 'doc': 0,
971 'docs': 1,
984 'docs': 1,
972 }
985 }
973
986
974 FALLBACK_PRIORITY = 99
987 FALLBACK_PRIORITY = 99
975
988
976 RENDERER_TO_EXTENSION = {
989 RENDERER_TO_EXTENSION = {
977 'rst': ['.rst', '.rest'],
990 'rst': ['.rst', '.rest'],
978 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
991 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
979 }
992 }
980
993
981 def __init__(self, default_renderer=None):
994 def __init__(self, default_renderer=None):
982 self._default_renderer = default_renderer
995 self._default_renderer = default_renderer
983 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
996 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
984 default_renderer, [])
997 default_renderer, [])
985
998
986 def search(self, commit, path='/'):
999 def search(self, commit, path='/'):
987 """
1000 """
988 Find a readme in the given `commit`.
1001 Find a readme in the given `commit`.
989 """
1002 """
990 nodes = commit.get_nodes(path)
1003 nodes = commit.get_nodes(path)
991 matches = self._match_readmes(nodes)
1004 matches = self._match_readmes(nodes)
992 matches = self._sort_according_to_priority(matches)
1005 matches = self._sort_according_to_priority(matches)
993 if matches:
1006 if matches:
994 return matches[0].node
1007 return matches[0].node
995
1008
996 paths = self._match_paths(nodes)
1009 paths = self._match_paths(nodes)
997 paths = self._sort_paths_according_to_priority(paths)
1010 paths = self._sort_paths_according_to_priority(paths)
998 for path in paths:
1011 for path in paths:
999 match = self.search(commit, path=path)
1012 match = self.search(commit, path=path)
1000 if match:
1013 if match:
1001 return match
1014 return match
1002
1015
1003 return None
1016 return None
1004
1017
1005 def _match_readmes(self, nodes):
1018 def _match_readmes(self, nodes):
1006 for node in nodes:
1019 for node in nodes:
1007 if not node.is_file():
1020 if not node.is_file():
1008 continue
1021 continue
1009 path = node.path.rsplit('/', 1)[-1]
1022 path = node.path.rsplit('/', 1)[-1]
1010 match = self.readme_re.match(path)
1023 match = self.readme_re.match(path)
1011 if match:
1024 if match:
1012 extension = match.group(1)
1025 extension = match.group(1)
1013 yield ReadmeMatch(node, match, self._priority(extension))
1026 yield ReadmeMatch(node, match, self._priority(extension))
1014
1027
1015 def _match_paths(self, nodes):
1028 def _match_paths(self, nodes):
1016 for node in nodes:
1029 for node in nodes:
1017 if not node.is_dir():
1030 if not node.is_dir():
1018 continue
1031 continue
1019 match = self.path_re.match(node.path)
1032 match = self.path_re.match(node.path)
1020 if match:
1033 if match:
1021 yield node.path
1034 yield node.path
1022
1035
1023 def _priority(self, extension):
1036 def _priority(self, extension):
1024 renderer_priority = (
1037 renderer_priority = (
1025 0 if extension in self._renderer_extensions else 1)
1038 0 if extension in self._renderer_extensions else 1)
1026 extension_priority = self.default_priorities.get(
1039 extension_priority = self.default_priorities.get(
1027 extension, self.FALLBACK_PRIORITY)
1040 extension, self.FALLBACK_PRIORITY)
1028 return (renderer_priority, extension_priority)
1041 return (renderer_priority, extension_priority)
1029
1042
1030 def _sort_according_to_priority(self, matches):
1043 def _sort_according_to_priority(self, matches):
1031
1044
1032 def priority_and_path(match):
1045 def priority_and_path(match):
1033 return (match.priority, match.path)
1046 return (match.priority, match.path)
1034
1047
1035 return sorted(matches, key=priority_and_path)
1048 return sorted(matches, key=priority_and_path)
1036
1049
1037 def _sort_paths_according_to_priority(self, paths):
1050 def _sort_paths_according_to_priority(self, paths):
1038
1051
1039 def priority_and_path(path):
1052 def priority_and_path(path):
1040 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1053 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1041
1054
1042 return sorted(paths, key=priority_and_path)
1055 return sorted(paths, key=priority_and_path)
1043
1056
1044
1057
1045 class ReadmeMatch:
1058 class ReadmeMatch:
1046
1059
1047 def __init__(self, node, match, priority):
1060 def __init__(self, node, match, priority):
1048 self.node = node
1061 self.node = node
1049 self._match = match
1062 self._match = match
1050 self.priority = priority
1063 self.priority = priority
1051
1064
1052 @property
1065 @property
1053 def path(self):
1066 def path(self):
1054 return self.node.path
1067 return self.node.path
1055
1068
1056 def __repr__(self):
1069 def __repr__(self):
1057 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1070 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
General Comments 0
You need to be logged in to leave comments. Login now