##// END OF EJS Templates
cleanup: get rid of redundant assignments 'foo = foo'...
Thomas De Schampheleire -
r8526:0b1a23b8 default
parent child Browse files
Show More
@@ -1,2443 +1,2442 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.api.api
15 kallithea.controllers.api.api
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 API controller for Kallithea
18 API controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Aug 20, 2011
22 :created_on: Aug 20, 2011
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30 from datetime import datetime
30 from datetime import datetime
31
31
32 from tg import request
32 from tg import request
33
33
34 from kallithea.controllers.api import JSONRPCController, JSONRPCError
34 from kallithea.controllers.api import JSONRPCController, JSONRPCError
35 from kallithea.lib.auth import (AuthUser, HasPermissionAny, HasPermissionAnyDecorator, HasRepoGroupPermissionLevel, HasRepoPermissionLevel,
35 from kallithea.lib.auth import (AuthUser, HasPermissionAny, HasPermissionAnyDecorator, HasRepoGroupPermissionLevel, HasRepoPermissionLevel,
36 HasUserGroupPermissionLevel)
36 HasUserGroupPermissionLevel)
37 from kallithea.lib.exceptions import DefaultUserException, UserGroupsAssignedException
37 from kallithea.lib.exceptions import DefaultUserException, UserGroupsAssignedException
38 from kallithea.lib.utils import repo2db_mapper
38 from kallithea.lib.utils import repo2db_mapper
39 from kallithea.lib.vcs.backends.base import EmptyChangeset
39 from kallithea.lib.vcs.backends.base import EmptyChangeset
40 from kallithea.lib.vcs.exceptions import EmptyRepositoryError
40 from kallithea.lib.vcs.exceptions import EmptyRepositoryError
41 from kallithea.model import db, meta, userlog
41 from kallithea.model import db, meta, userlog
42 from kallithea.model.changeset_status import ChangesetStatusModel
42 from kallithea.model.changeset_status import ChangesetStatusModel
43 from kallithea.model.comment import ChangesetCommentsModel
43 from kallithea.model.comment import ChangesetCommentsModel
44 from kallithea.model.gist import GistModel
44 from kallithea.model.gist import GistModel
45 from kallithea.model.pull_request import PullRequestModel
45 from kallithea.model.pull_request import PullRequestModel
46 from kallithea.model.repo import RepoModel
46 from kallithea.model.repo import RepoModel
47 from kallithea.model.repo_group import RepoGroupModel
47 from kallithea.model.repo_group import RepoGroupModel
48 from kallithea.model.scm import ScmModel, UserGroupList
48 from kallithea.model.scm import ScmModel, UserGroupList
49 from kallithea.model.user import UserModel
49 from kallithea.model.user import UserModel
50 from kallithea.model.user_group import UserGroupModel
50 from kallithea.model.user_group import UserGroupModel
51
51
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 def store_update(updates, attr, name):
56 def store_update(updates, attr, name):
57 """
57 """
58 Stores param in updates dict if it's not None (i.e. if user explicitly set
58 Stores param in updates dict if it's not None (i.e. if user explicitly set
59 a parameter). This allows easy updates of passed in params.
59 a parameter). This allows easy updates of passed in params.
60 """
60 """
61 if attr is not None:
61 if attr is not None:
62 updates[name] = attr
62 updates[name] = attr
63
63
64
64
65 def get_user_or_error(userid):
65 def get_user_or_error(userid):
66 """
66 """
67 Get user by id or name or return JsonRPCError if not found
67 Get user by id or name or return JsonRPCError if not found
68
68
69 :param userid:
69 :param userid:
70 """
70 """
71 user = UserModel().get_user(userid)
71 user = UserModel().get_user(userid)
72 if user is None:
72 if user is None:
73 raise JSONRPCError("user `%s` does not exist" % (userid,))
73 raise JSONRPCError("user `%s` does not exist" % (userid,))
74 return user
74 return user
75
75
76
76
77 def get_repo_or_error(repoid):
77 def get_repo_or_error(repoid):
78 """
78 """
79 Get repo by id or name or return JsonRPCError if not found
79 Get repo by id or name or return JsonRPCError if not found
80
80
81 :param repoid:
81 :param repoid:
82 """
82 """
83 repo = RepoModel().get_repo(repoid)
83 repo = RepoModel().get_repo(repoid)
84 if repo is None:
84 if repo is None:
85 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
85 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
86 return repo
86 return repo
87
87
88
88
89 def get_repo_group_or_error(repogroupid):
89 def get_repo_group_or_error(repogroupid):
90 """
90 """
91 Get repo group by id or name or return JsonRPCError if not found
91 Get repo group by id or name or return JsonRPCError if not found
92
92
93 :param repogroupid:
93 :param repogroupid:
94 """
94 """
95 repo_group = db.RepoGroup.guess_instance(repogroupid)
95 repo_group = db.RepoGroup.guess_instance(repogroupid)
96 if repo_group is None:
96 if repo_group is None:
97 raise JSONRPCError(
97 raise JSONRPCError(
98 'repository group `%s` does not exist' % (repogroupid,))
98 'repository group `%s` does not exist' % (repogroupid,))
99 return repo_group
99 return repo_group
100
100
101
101
102 def get_user_group_or_error(usergroupid):
102 def get_user_group_or_error(usergroupid):
103 """
103 """
104 Get user group by id or name or return JsonRPCError if not found
104 Get user group by id or name or return JsonRPCError if not found
105
105
106 :param usergroupid:
106 :param usergroupid:
107 """
107 """
108 user_group = UserGroupModel().get_group(usergroupid)
108 user_group = UserGroupModel().get_group(usergroupid)
109 if user_group is None:
109 if user_group is None:
110 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
110 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
111 return user_group
111 return user_group
112
112
113
113
114 def get_perm_or_error(permid, prefix=None):
114 def get_perm_or_error(permid, prefix=None):
115 """
115 """
116 Get permission by id or name or return JsonRPCError if not found
116 Get permission by id or name or return JsonRPCError if not found
117
117
118 :param permid:
118 :param permid:
119 """
119 """
120 perm = db.Permission.get_by_key(permid)
120 perm = db.Permission.get_by_key(permid)
121 if perm is None:
121 if perm is None:
122 raise JSONRPCError('permission `%s` does not exist' % (permid,))
122 raise JSONRPCError('permission `%s` does not exist' % (permid,))
123 if prefix:
123 if prefix:
124 if not perm.permission_name.startswith(prefix):
124 if not perm.permission_name.startswith(prefix):
125 raise JSONRPCError('permission `%s` is invalid, '
125 raise JSONRPCError('permission `%s` is invalid, '
126 'should start with %s' % (permid, prefix))
126 'should start with %s' % (permid, prefix))
127 return perm
127 return perm
128
128
129
129
130 def get_gist_or_error(gistid):
130 def get_gist_or_error(gistid):
131 """
131 """
132 Get gist by id or gist_access_id or return JsonRPCError if not found
132 Get gist by id or gist_access_id or return JsonRPCError if not found
133
133
134 :param gistid:
134 :param gistid:
135 """
135 """
136 gist = GistModel().get_gist(gistid)
136 gist = GistModel().get_gist(gistid)
137 if gist is None:
137 if gist is None:
138 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
138 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
139 return gist
139 return gist
140
140
141
141
142 class ApiController(JSONRPCController):
142 class ApiController(JSONRPCController):
143 """
143 """
144 API Controller
144 API Controller
145
145
146 The authenticated user can be found as request.authuser.
146 The authenticated user can be found as request.authuser.
147
147
148 Example function::
148 Example function::
149
149
150 def func(arg1, arg2,...):
150 def func(arg1, arg2,...):
151 pass
151 pass
152
152
153 Each function should also **raise** JSONRPCError for any
153 Each function should also **raise** JSONRPCError for any
154 errors that happens.
154 errors that happens.
155 """
155 """
156
156
157 @HasPermissionAnyDecorator('hg.admin')
157 @HasPermissionAnyDecorator('hg.admin')
158 def test(self, args):
158 def test(self, args):
159 return args
159 return args
160
160
161 @HasPermissionAnyDecorator('hg.admin')
161 @HasPermissionAnyDecorator('hg.admin')
162 def pull(self, repoid, clone_uri=None):
162 def pull(self, repoid, clone_uri=None):
163 """
163 """
164 Triggers a pull from remote location on given repo. Can be used to
164 Triggers a pull from remote location on given repo. Can be used to
165 automatically keep remote repos up to date. This command can be executed
165 automatically keep remote repos up to date. This command can be executed
166 only using api_key belonging to user with admin rights
166 only using api_key belonging to user with admin rights
167
167
168 :param repoid: repository name or repository id
168 :param repoid: repository name or repository id
169 :type repoid: str or int
169 :type repoid: str or int
170 :param clone_uri: repository URI to pull from (optional)
170 :param clone_uri: repository URI to pull from (optional)
171 :type clone_uri: str
171 :type clone_uri: str
172
172
173 OUTPUT::
173 OUTPUT::
174
174
175 id : <id_given_in_input>
175 id : <id_given_in_input>
176 result : {
176 result : {
177 "msg": "Pulled from `<repository name>`"
177 "msg": "Pulled from `<repository name>`"
178 "repository": "<repository name>"
178 "repository": "<repository name>"
179 }
179 }
180 error : null
180 error : null
181
181
182 ERROR OUTPUT::
182 ERROR OUTPUT::
183
183
184 id : <id_given_in_input>
184 id : <id_given_in_input>
185 result : null
185 result : null
186 error : {
186 error : {
187 "Unable to pull changes from `<reponame>`"
187 "Unable to pull changes from `<reponame>`"
188 }
188 }
189
189
190 """
190 """
191
191
192 repo = get_repo_or_error(repoid)
192 repo = get_repo_or_error(repoid)
193
193
194 try:
194 try:
195 ScmModel().pull_changes(repo.repo_name,
195 ScmModel().pull_changes(repo.repo_name,
196 request.authuser.username,
196 request.authuser.username,
197 request.ip_addr,
197 request.ip_addr,
198 clone_uri=clone_uri)
198 clone_uri=clone_uri)
199 return dict(
199 return dict(
200 msg='Pulled from `%s`' % repo.repo_name,
200 msg='Pulled from `%s`' % repo.repo_name,
201 repository=repo.repo_name
201 repository=repo.repo_name
202 )
202 )
203 except Exception:
203 except Exception:
204 log.error(traceback.format_exc())
204 log.error(traceback.format_exc())
205 raise JSONRPCError(
205 raise JSONRPCError(
206 'Unable to pull changes from `%s`' % repo.repo_name
206 'Unable to pull changes from `%s`' % repo.repo_name
207 )
207 )
208
208
209 @HasPermissionAnyDecorator('hg.admin')
209 @HasPermissionAnyDecorator('hg.admin')
210 def rescan_repos(self, remove_obsolete=False):
210 def rescan_repos(self, remove_obsolete=False):
211 """
211 """
212 Triggers rescan repositories action. If remove_obsolete is set
212 Triggers rescan repositories action. If remove_obsolete is set
213 than also delete repos that are in database but not in the filesystem.
213 than also delete repos that are in database but not in the filesystem.
214 aka "clean zombies". This command can be executed only using api_key
214 aka "clean zombies". This command can be executed only using api_key
215 belonging to user with admin rights.
215 belonging to user with admin rights.
216
216
217 :param remove_obsolete: deletes repositories from
217 :param remove_obsolete: deletes repositories from
218 database that are not found on the filesystem
218 database that are not found on the filesystem
219 :type remove_obsolete: Optional(bool)
219 :type remove_obsolete: Optional(bool)
220
220
221 OUTPUT::
221 OUTPUT::
222
222
223 id : <id_given_in_input>
223 id : <id_given_in_input>
224 result : {
224 result : {
225 'added': [<added repository name>,...]
225 'added': [<added repository name>,...]
226 'removed': [<removed repository name>,...]
226 'removed': [<removed repository name>,...]
227 }
227 }
228 error : null
228 error : null
229
229
230 ERROR OUTPUT::
230 ERROR OUTPUT::
231
231
232 id : <id_given_in_input>
232 id : <id_given_in_input>
233 result : null
233 result : null
234 error : {
234 error : {
235 'Error occurred during rescan repositories action'
235 'Error occurred during rescan repositories action'
236 }
236 }
237
237
238 """
238 """
239
239
240 try:
240 try:
241 rm_obsolete = remove_obsolete
241 rm_obsolete = remove_obsolete
242 added, removed = repo2db_mapper(ScmModel().repo_scan(),
242 added, removed = repo2db_mapper(ScmModel().repo_scan(),
243 remove_obsolete=rm_obsolete)
243 remove_obsolete=rm_obsolete)
244 return {'added': added, 'removed': removed}
244 return {'added': added, 'removed': removed}
245 except Exception:
245 except Exception:
246 log.error(traceback.format_exc())
246 log.error(traceback.format_exc())
247 raise JSONRPCError(
247 raise JSONRPCError(
248 'Error occurred during rescan repositories action'
248 'Error occurred during rescan repositories action'
249 )
249 )
250
250
251 def invalidate_cache(self, repoid):
251 def invalidate_cache(self, repoid):
252 """
252 """
253 Invalidate cache for repository.
253 Invalidate cache for repository.
254 This command can be executed only using api_key belonging to user with admin
254 This command can be executed only using api_key belonging to user with admin
255 rights or regular user that have write or admin or write access to repository.
255 rights or regular user that have write or admin or write access to repository.
256
256
257 :param repoid: repository name or repository id
257 :param repoid: repository name or repository id
258 :type repoid: str or int
258 :type repoid: str or int
259
259
260 OUTPUT::
260 OUTPUT::
261
261
262 id : <id_given_in_input>
262 id : <id_given_in_input>
263 result : {
263 result : {
264 'msg': Cache for repository `<repository name>` was invalidated,
264 'msg': Cache for repository `<repository name>` was invalidated,
265 'repository': <repository name>
265 'repository': <repository name>
266 }
266 }
267 error : null
267 error : null
268
268
269 ERROR OUTPUT::
269 ERROR OUTPUT::
270
270
271 id : <id_given_in_input>
271 id : <id_given_in_input>
272 result : null
272 result : null
273 error : {
273 error : {
274 'Error occurred during cache invalidation action'
274 'Error occurred during cache invalidation action'
275 }
275 }
276
276
277 """
277 """
278 repo = get_repo_or_error(repoid)
278 repo = get_repo_or_error(repoid)
279 if not HasPermissionAny('hg.admin')():
279 if not HasPermissionAny('hg.admin')():
280 if not HasRepoPermissionLevel('write')(repo.repo_name):
280 if not HasRepoPermissionLevel('write')(repo.repo_name):
281 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
281 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
282
282
283 try:
283 try:
284 ScmModel().mark_for_invalidation(repo.repo_name)
284 ScmModel().mark_for_invalidation(repo.repo_name)
285 return dict(
285 return dict(
286 msg='Cache for repository `%s` was invalidated' % (repoid,),
286 msg='Cache for repository `%s` was invalidated' % (repoid,),
287 repository=repo.repo_name
287 repository=repo.repo_name
288 )
288 )
289 except Exception:
289 except Exception:
290 log.error(traceback.format_exc())
290 log.error(traceback.format_exc())
291 raise JSONRPCError(
291 raise JSONRPCError(
292 'Error occurred during cache invalidation action'
292 'Error occurred during cache invalidation action'
293 )
293 )
294
294
295 @HasPermissionAnyDecorator('hg.admin')
295 @HasPermissionAnyDecorator('hg.admin')
296 def get_ip(self, userid=None):
296 def get_ip(self, userid=None):
297 """
297 """
298 Shows IP address as seen from Kallithea server, together with all
298 Shows IP address as seen from Kallithea server, together with all
299 defined IP addresses for given user. If userid is not passed data is
299 defined IP addresses for given user. If userid is not passed data is
300 returned for user who's calling this function.
300 returned for user who's calling this function.
301 This command can be executed only using api_key belonging to user with
301 This command can be executed only using api_key belonging to user with
302 admin rights.
302 admin rights.
303
303
304 :param userid: username to show ips for
304 :param userid: username to show ips for
305 :type userid: Optional(str or int)
305 :type userid: Optional(str or int)
306
306
307 OUTPUT::
307 OUTPUT::
308
308
309 id : <id_given_in_input>
309 id : <id_given_in_input>
310 result : {
310 result : {
311 "server_ip_addr": "<ip_from_clien>",
311 "server_ip_addr": "<ip_from_clien>",
312 "user_ips": [
312 "user_ips": [
313 {
313 {
314 "ip_addr": "<ip_with_mask>",
314 "ip_addr": "<ip_with_mask>",
315 "ip_range": ["<start_ip>", "<end_ip>"],
315 "ip_range": ["<start_ip>", "<end_ip>"],
316 },
316 },
317 ...
317 ...
318 ]
318 ]
319 }
319 }
320
320
321 """
321 """
322 if userid is None:
322 if userid is None:
323 userid = request.authuser.user_id
323 userid = request.authuser.user_id
324 user = get_user_or_error(userid)
324 user = get_user_or_error(userid)
325 ips = db.UserIpMap.query().filter(db.UserIpMap.user == user).all()
325 ips = db.UserIpMap.query().filter(db.UserIpMap.user == user).all()
326 return dict(
326 return dict(
327 server_ip_addr=request.ip_addr,
327 server_ip_addr=request.ip_addr,
328 user_ips=ips
328 user_ips=ips
329 )
329 )
330
330
331 # alias for old
331 # alias for old
332 show_ip = get_ip
332 show_ip = get_ip
333
333
334 @HasPermissionAnyDecorator('hg.admin')
334 @HasPermissionAnyDecorator('hg.admin')
335 def get_server_info(self):
335 def get_server_info(self):
336 """
336 """
337 return server info, including Kallithea version and installed packages
337 return server info, including Kallithea version and installed packages
338
338
339
339
340 OUTPUT::
340 OUTPUT::
341
341
342 id : <id_given_in_input>
342 id : <id_given_in_input>
343 result : {
343 result : {
344 'modules': [<module name>,...]
344 'modules': [<module name>,...]
345 'py_version': <python version>,
345 'py_version': <python version>,
346 'platform': <platform type>,
346 'platform': <platform type>,
347 'kallithea_version': <kallithea version>
347 'kallithea_version': <kallithea version>
348 }
348 }
349 error : null
349 error : null
350 """
350 """
351 return db.Setting.get_server_info()
351 return db.Setting.get_server_info()
352
352
353 def get_user(self, userid=None):
353 def get_user(self, userid=None):
354 """
354 """
355 Gets a user by username or user_id, Returns empty result if user is
355 Gets a user by username or user_id, Returns empty result if user is
356 not found. If userid param is skipped it is set to id of user who is
356 not found. If userid param is skipped it is set to id of user who is
357 calling this method. This command can be executed only using api_key
357 calling this method. This command can be executed only using api_key
358 belonging to user with admin rights, or regular users that cannot
358 belonging to user with admin rights, or regular users that cannot
359 specify different userid than theirs
359 specify different userid than theirs
360
360
361 :param userid: user to get data for
361 :param userid: user to get data for
362 :type userid: Optional(str or int)
362 :type userid: Optional(str or int)
363
363
364 OUTPUT::
364 OUTPUT::
365
365
366 id : <id_given_in_input>
366 id : <id_given_in_input>
367 result: None if user does not exist or
367 result: None if user does not exist or
368 {
368 {
369 "user_id" : "<user_id>",
369 "user_id" : "<user_id>",
370 "api_key" : "<api_key>",
370 "api_key" : "<api_key>",
371 "api_keys": "[<list of all API keys including additional ones>]"
371 "api_keys": "[<list of all API keys including additional ones>]"
372 "username" : "<username>",
372 "username" : "<username>",
373 "firstname": "<firstname>",
373 "firstname": "<firstname>",
374 "lastname" : "<lastname>",
374 "lastname" : "<lastname>",
375 "email" : "<email>",
375 "email" : "<email>",
376 "emails": "[<list of all emails including additional ones>]",
376 "emails": "[<list of all emails including additional ones>]",
377 "ip_addresses": "[<ip_address_for_user>,...]",
377 "ip_addresses": "[<ip_address_for_user>,...]",
378 "active" : "<bool: user active>",
378 "active" : "<bool: user active>",
379 "admin" :Β  "<bool: user is admin>",
379 "admin" :Β  "<bool: user is admin>",
380 "extern_name" : "<extern_name>",
380 "extern_name" : "<extern_name>",
381 "extern_type" : "<extern type>
381 "extern_type" : "<extern type>
382 "last_login": "<last_login>",
382 "last_login": "<last_login>",
383 "permissions": {
383 "permissions": {
384 "global": ["hg.create.repository",
384 "global": ["hg.create.repository",
385 "repository.read",
385 "repository.read",
386 "hg.register.manual_activate"],
386 "hg.register.manual_activate"],
387 "repositories": {"repo1": "repository.none"},
387 "repositories": {"repo1": "repository.none"},
388 "repositories_groups": {"Group1": "group.read"}
388 "repositories_groups": {"Group1": "group.read"}
389 },
389 },
390 }
390 }
391
391
392 error: null
392 error: null
393
393
394 """
394 """
395 if not HasPermissionAny('hg.admin')():
395 if not HasPermissionAny('hg.admin')():
396 # make sure normal user does not pass someone else userid,
396 # make sure normal user does not pass someone else userid,
397 # he is not allowed to do that
397 # he is not allowed to do that
398 if userid is not None and userid != request.authuser.user_id:
398 if userid is not None and userid != request.authuser.user_id:
399 raise JSONRPCError(
399 raise JSONRPCError(
400 'userid is not the same as your user'
400 'userid is not the same as your user'
401 )
401 )
402
402
403 if userid is None:
403 if userid is None:
404 userid = request.authuser.user_id
404 userid = request.authuser.user_id
405
405
406 user = get_user_or_error(userid)
406 user = get_user_or_error(userid)
407 data = user.get_api_data()
407 data = user.get_api_data()
408 data['permissions'] = AuthUser(user_id=user.user_id).permissions
408 data['permissions'] = AuthUser(user_id=user.user_id).permissions
409 return data
409 return data
410
410
411 @HasPermissionAnyDecorator('hg.admin')
411 @HasPermissionAnyDecorator('hg.admin')
412 def get_users(self):
412 def get_users(self):
413 """
413 """
414 Lists all existing users. This command can be executed only using api_key
414 Lists all existing users. This command can be executed only using api_key
415 belonging to user with admin rights.
415 belonging to user with admin rights.
416
416
417
417
418 OUTPUT::
418 OUTPUT::
419
419
420 id : <id_given_in_input>
420 id : <id_given_in_input>
421 result: [<user_object>, ...]
421 result: [<user_object>, ...]
422 error: null
422 error: null
423 """
423 """
424
424
425 return [
425 return [
426 user.get_api_data()
426 user.get_api_data()
427 for user in db.User.query()
427 for user in db.User.query()
428 .order_by(db.User.username)
428 .order_by(db.User.username)
429 .filter_by(is_default_user=False)
429 .filter_by(is_default_user=False)
430 ]
430 ]
431
431
432 @HasPermissionAnyDecorator('hg.admin')
432 @HasPermissionAnyDecorator('hg.admin')
433 def create_user(self, username, email, password='',
433 def create_user(self, username, email, password='',
434 firstname='', lastname='',
434 firstname='', lastname='',
435 active=True, admin=False,
435 active=True, admin=False,
436 extern_type=db.User.DEFAULT_AUTH_TYPE,
436 extern_type=db.User.DEFAULT_AUTH_TYPE,
437 extern_name=''):
437 extern_name=''):
438 """
438 """
439 Creates new user. Returns new user object. This command can
439 Creates new user. Returns new user object. This command can
440 be executed only using api_key belonging to user with admin rights.
440 be executed only using api_key belonging to user with admin rights.
441
441
442 :param username: new username
442 :param username: new username
443 :type username: str or int
443 :type username: str or int
444 :param email: email
444 :param email: email
445 :type email: str
445 :type email: str
446 :param password: password
446 :param password: password
447 :type password: Optional(str)
447 :type password: Optional(str)
448 :param firstname: firstname
448 :param firstname: firstname
449 :type firstname: Optional(str)
449 :type firstname: Optional(str)
450 :param lastname: lastname
450 :param lastname: lastname
451 :type lastname: Optional(str)
451 :type lastname: Optional(str)
452 :param active: active
452 :param active: active
453 :type active: Optional(bool)
453 :type active: Optional(bool)
454 :param admin: admin
454 :param admin: admin
455 :type admin: Optional(bool)
455 :type admin: Optional(bool)
456 :param extern_name: name of extern
456 :param extern_name: name of extern
457 :type extern_name: Optional(str)
457 :type extern_name: Optional(str)
458 :param extern_type: extern_type
458 :param extern_type: extern_type
459 :type extern_type: Optional(str)
459 :type extern_type: Optional(str)
460
460
461
461
462 OUTPUT::
462 OUTPUT::
463
463
464 id : <id_given_in_input>
464 id : <id_given_in_input>
465 result: {
465 result: {
466 "msg" : "created new user `<username>`",
466 "msg" : "created new user `<username>`",
467 "user": <user_obj>
467 "user": <user_obj>
468 }
468 }
469 error: null
469 error: null
470
470
471 ERROR OUTPUT::
471 ERROR OUTPUT::
472
472
473 id : <id_given_in_input>
473 id : <id_given_in_input>
474 result : null
474 result : null
475 error : {
475 error : {
476 "user `<username>` already exist"
476 "user `<username>` already exist"
477 or
477 or
478 "email `<email>` already exist"
478 "email `<email>` already exist"
479 or
479 or
480 "failed to create user `<username>`"
480 "failed to create user `<username>`"
481 }
481 }
482
482
483 """
483 """
484
484
485 if db.User.get_by_username(username):
485 if db.User.get_by_username(username):
486 raise JSONRPCError("user `%s` already exist" % (username,))
486 raise JSONRPCError("user `%s` already exist" % (username,))
487
487
488 if db.User.get_by_email(email):
488 if db.User.get_by_email(email):
489 raise JSONRPCError("email `%s` already exist" % (email,))
489 raise JSONRPCError("email `%s` already exist" % (email,))
490
490
491 try:
491 try:
492 user = UserModel().create_or_update(
492 user = UserModel().create_or_update(
493 username=username,
493 username=username,
494 password=password,
494 password=password,
495 email=email,
495 email=email,
496 firstname=firstname,
496 firstname=firstname,
497 lastname=lastname,
497 lastname=lastname,
498 active=active,
498 active=active,
499 admin=admin,
499 admin=admin,
500 extern_type=extern_type,
500 extern_type=extern_type,
501 extern_name=extern_name
501 extern_name=extern_name
502 )
502 )
503 meta.Session().commit()
503 meta.Session().commit()
504 return dict(
504 return dict(
505 msg='created new user `%s`' % username,
505 msg='created new user `%s`' % username,
506 user=user.get_api_data()
506 user=user.get_api_data()
507 )
507 )
508 except Exception:
508 except Exception:
509 log.error(traceback.format_exc())
509 log.error(traceback.format_exc())
510 raise JSONRPCError('failed to create user `%s`' % (username,))
510 raise JSONRPCError('failed to create user `%s`' % (username,))
511
511
512 @HasPermissionAnyDecorator('hg.admin')
512 @HasPermissionAnyDecorator('hg.admin')
513 def update_user(self, userid, username=None,
513 def update_user(self, userid, username=None,
514 email=None, password=None,
514 email=None, password=None,
515 firstname=None, lastname=None,
515 firstname=None, lastname=None,
516 active=None, admin=None,
516 active=None, admin=None,
517 extern_type=None, extern_name=None):
517 extern_type=None, extern_name=None):
518 """
518 """
519 updates given user if such user exists. This command can
519 updates given user if such user exists. This command can
520 be executed only using api_key belonging to user with admin rights.
520 be executed only using api_key belonging to user with admin rights.
521
521
522 :param userid: userid to update
522 :param userid: userid to update
523 :type userid: str or int
523 :type userid: str or int
524 :param username: new username
524 :param username: new username
525 :type username: str or int
525 :type username: str or int
526 :param email: email
526 :param email: email
527 :type email: str
527 :type email: str
528 :param password: password
528 :param password: password
529 :type password: Optional(str)
529 :type password: Optional(str)
530 :param firstname: firstname
530 :param firstname: firstname
531 :type firstname: Optional(str)
531 :type firstname: Optional(str)
532 :param lastname: lastname
532 :param lastname: lastname
533 :type lastname: Optional(str)
533 :type lastname: Optional(str)
534 :param active: active
534 :param active: active
535 :type active: Optional(bool)
535 :type active: Optional(bool)
536 :param admin: admin
536 :param admin: admin
537 :type admin: Optional(bool)
537 :type admin: Optional(bool)
538 :param extern_name:
538 :param extern_name:
539 :type extern_name: Optional(str)
539 :type extern_name: Optional(str)
540 :param extern_type:
540 :param extern_type:
541 :type extern_type: Optional(str)
541 :type extern_type: Optional(str)
542
542
543
543
544 OUTPUT::
544 OUTPUT::
545
545
546 id : <id_given_in_input>
546 id : <id_given_in_input>
547 result: {
547 result: {
548 "msg" : "updated user ID:<userid> <username>",
548 "msg" : "updated user ID:<userid> <username>",
549 "user": <user_object>,
549 "user": <user_object>,
550 }
550 }
551 error: null
551 error: null
552
552
553 ERROR OUTPUT::
553 ERROR OUTPUT::
554
554
555 id : <id_given_in_input>
555 id : <id_given_in_input>
556 result : null
556 result : null
557 error : {
557 error : {
558 "failed to update user `<username>`"
558 "failed to update user `<username>`"
559 }
559 }
560
560
561 """
561 """
562
562
563 user = get_user_or_error(userid)
563 user = get_user_or_error(userid)
564
564
565 # only non optional arguments will be stored in updates
565 # only non optional arguments will be stored in updates
566 updates = {}
566 updates = {}
567
567
568 try:
568 try:
569
569
570 store_update(updates, username, 'username')
570 store_update(updates, username, 'username')
571 store_update(updates, password, 'password')
571 store_update(updates, password, 'password')
572 store_update(updates, email, 'email')
572 store_update(updates, email, 'email')
573 store_update(updates, firstname, 'name')
573 store_update(updates, firstname, 'name')
574 store_update(updates, lastname, 'lastname')
574 store_update(updates, lastname, 'lastname')
575 store_update(updates, active, 'active')
575 store_update(updates, active, 'active')
576 store_update(updates, admin, 'admin')
576 store_update(updates, admin, 'admin')
577 store_update(updates, extern_name, 'extern_name')
577 store_update(updates, extern_name, 'extern_name')
578 store_update(updates, extern_type, 'extern_type')
578 store_update(updates, extern_type, 'extern_type')
579
579
580 user = UserModel().update_user(user, **updates)
580 user = UserModel().update_user(user, **updates)
581 meta.Session().commit()
581 meta.Session().commit()
582 return dict(
582 return dict(
583 msg='updated user ID:%s %s' % (user.user_id, user.username),
583 msg='updated user ID:%s %s' % (user.user_id, user.username),
584 user=user.get_api_data()
584 user=user.get_api_data()
585 )
585 )
586 except DefaultUserException:
586 except DefaultUserException:
587 log.error(traceback.format_exc())
587 log.error(traceback.format_exc())
588 raise JSONRPCError('editing default user is forbidden')
588 raise JSONRPCError('editing default user is forbidden')
589 except Exception:
589 except Exception:
590 log.error(traceback.format_exc())
590 log.error(traceback.format_exc())
591 raise JSONRPCError('failed to update user `%s`' % (userid,))
591 raise JSONRPCError('failed to update user `%s`' % (userid,))
592
592
593 @HasPermissionAnyDecorator('hg.admin')
593 @HasPermissionAnyDecorator('hg.admin')
594 def delete_user(self, userid):
594 def delete_user(self, userid):
595 """
595 """
596 deletes given user if such user exists. This command can
596 deletes given user if such user exists. This command can
597 be executed only using api_key belonging to user with admin rights.
597 be executed only using api_key belonging to user with admin rights.
598
598
599 :param userid: user to delete
599 :param userid: user to delete
600 :type userid: str or int
600 :type userid: str or int
601
601
602 OUTPUT::
602 OUTPUT::
603
603
604 id : <id_given_in_input>
604 id : <id_given_in_input>
605 result: {
605 result: {
606 "msg" : "deleted user ID:<userid> <username>",
606 "msg" : "deleted user ID:<userid> <username>",
607 "user": null
607 "user": null
608 }
608 }
609 error: null
609 error: null
610
610
611 ERROR OUTPUT::
611 ERROR OUTPUT::
612
612
613 id : <id_given_in_input>
613 id : <id_given_in_input>
614 result : null
614 result : null
615 error : {
615 error : {
616 "failed to delete user ID:<userid> <username>"
616 "failed to delete user ID:<userid> <username>"
617 }
617 }
618
618
619 """
619 """
620 user = get_user_or_error(userid)
620 user = get_user_or_error(userid)
621
621
622 try:
622 try:
623 UserModel().delete(userid)
623 UserModel().delete(userid)
624 meta.Session().commit()
624 meta.Session().commit()
625 return dict(
625 return dict(
626 msg='deleted user ID:%s %s' % (user.user_id, user.username),
626 msg='deleted user ID:%s %s' % (user.user_id, user.username),
627 user=None
627 user=None
628 )
628 )
629 except Exception:
629 except Exception:
630
630
631 log.error(traceback.format_exc())
631 log.error(traceback.format_exc())
632 raise JSONRPCError('failed to delete user ID:%s %s'
632 raise JSONRPCError('failed to delete user ID:%s %s'
633 % (user.user_id, user.username))
633 % (user.user_id, user.username))
634
634
635 # permission check inside
635 # permission check inside
636 def get_user_group(self, usergroupid):
636 def get_user_group(self, usergroupid):
637 """
637 """
638 Gets an existing user group. This command can be executed only using api_key
638 Gets an existing user group. This command can be executed only using api_key
639 belonging to user with admin rights or user who has at least
639 belonging to user with admin rights or user who has at least
640 read access to user group.
640 read access to user group.
641
641
642 :param usergroupid: id of user_group to edit
642 :param usergroupid: id of user_group to edit
643 :type usergroupid: str or int
643 :type usergroupid: str or int
644
644
645 OUTPUT::
645 OUTPUT::
646
646
647 id : <id_given_in_input>
647 id : <id_given_in_input>
648 result : None if group not exist
648 result : None if group not exist
649 {
649 {
650 "users_group_id" : "<id>",
650 "users_group_id" : "<id>",
651 "group_name" : "<groupname>",
651 "group_name" : "<groupname>",
652 "active": "<bool>",
652 "active": "<bool>",
653 "members" : [<user_obj>,...]
653 "members" : [<user_obj>,...]
654 }
654 }
655 error : null
655 error : null
656
656
657 """
657 """
658 user_group = get_user_group_or_error(usergroupid)
658 user_group = get_user_group_or_error(usergroupid)
659 if not HasPermissionAny('hg.admin')():
659 if not HasPermissionAny('hg.admin')():
660 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
660 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
661 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
661 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
662
662
663 data = user_group.get_api_data()
663 data = user_group.get_api_data()
664 return data
664 return data
665
665
666 # permission check inside
666 # permission check inside
667 def get_user_groups(self):
667 def get_user_groups(self):
668 """
668 """
669 Lists all existing user groups. This command can be executed only using
669 Lists all existing user groups. This command can be executed only using
670 api_key belonging to user with admin rights or user who has at least
670 api_key belonging to user with admin rights or user who has at least
671 read access to user group.
671 read access to user group.
672
672
673
673
674 OUTPUT::
674 OUTPUT::
675
675
676 id : <id_given_in_input>
676 id : <id_given_in_input>
677 result : [<user_group_obj>,...]
677 result : [<user_group_obj>,...]
678 error : null
678 error : null
679 """
679 """
680
680
681 return [
681 return [
682 user_group.get_api_data()
682 user_group.get_api_data()
683 for user_group in UserGroupList(db.UserGroup.query().all(), perm_level='read')
683 for user_group in UserGroupList(db.UserGroup.query().all(), perm_level='read')
684 ]
684 ]
685
685
686 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
686 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
687 def create_user_group(self, group_name, description='',
687 def create_user_group(self, group_name, description='',
688 owner=None, active=True):
688 owner=None, active=True):
689 """
689 """
690 Creates new user group. This command can be executed only using api_key
690 Creates new user group. This command can be executed only using api_key
691 belonging to user with admin rights or an user who has create user group
691 belonging to user with admin rights or an user who has create user group
692 permission
692 permission
693
693
694 :param group_name: name of new user group
694 :param group_name: name of new user group
695 :type group_name: str
695 :type group_name: str
696 :param description: group description
696 :param description: group description
697 :type description: str
697 :type description: str
698 :param owner: owner of group. If not passed apiuser is the owner
698 :param owner: owner of group. If not passed apiuser is the owner
699 :type owner: Optional(str or int)
699 :type owner: Optional(str or int)
700 :param active: group is active
700 :param active: group is active
701 :type active: Optional(bool)
701 :type active: Optional(bool)
702
702
703 OUTPUT::
703 OUTPUT::
704
704
705 id : <id_given_in_input>
705 id : <id_given_in_input>
706 result: {
706 result: {
707 "msg": "created new user group `<groupname>`",
707 "msg": "created new user group `<groupname>`",
708 "user_group": <user_group_object>
708 "user_group": <user_group_object>
709 }
709 }
710 error: null
710 error: null
711
711
712 ERROR OUTPUT::
712 ERROR OUTPUT::
713
713
714 id : <id_given_in_input>
714 id : <id_given_in_input>
715 result : null
715 result : null
716 error : {
716 error : {
717 "user group `<group name>` already exist"
717 "user group `<group name>` already exist"
718 or
718 or
719 "failed to create group `<group name>`"
719 "failed to create group `<group name>`"
720 }
720 }
721
721
722 """
722 """
723
723
724 if UserGroupModel().get_by_name(group_name):
724 if UserGroupModel().get_by_name(group_name):
725 raise JSONRPCError("user group `%s` already exist" % (group_name,))
725 raise JSONRPCError("user group `%s` already exist" % (group_name,))
726
726
727 try:
727 try:
728 if owner is None:
728 if owner is None:
729 owner = request.authuser.user_id
729 owner = request.authuser.user_id
730
730
731 owner = get_user_or_error(owner)
731 owner = get_user_or_error(owner)
732 ug = UserGroupModel().create(name=group_name, description=description,
732 ug = UserGroupModel().create(name=group_name, description=description,
733 owner=owner, active=active)
733 owner=owner, active=active)
734 meta.Session().commit()
734 meta.Session().commit()
735 return dict(
735 return dict(
736 msg='created new user group `%s`' % group_name,
736 msg='created new user group `%s`' % group_name,
737 user_group=ug.get_api_data()
737 user_group=ug.get_api_data()
738 )
738 )
739 except Exception:
739 except Exception:
740 log.error(traceback.format_exc())
740 log.error(traceback.format_exc())
741 raise JSONRPCError('failed to create group `%s`' % (group_name,))
741 raise JSONRPCError('failed to create group `%s`' % (group_name,))
742
742
743 # permission check inside
743 # permission check inside
744 def update_user_group(self, usergroupid, group_name=None,
744 def update_user_group(self, usergroupid, group_name=None,
745 description=None, owner=None,
745 description=None, owner=None,
746 active=None):
746 active=None):
747 """
747 """
748 Updates given usergroup. This command can be executed only using api_key
748 Updates given usergroup. This command can be executed only using api_key
749 belonging to user with admin rights or an admin of given user group
749 belonging to user with admin rights or an admin of given user group
750
750
751 :param usergroupid: id of user group to update
751 :param usergroupid: id of user group to update
752 :type usergroupid: str or int
752 :type usergroupid: str or int
753 :param group_name: name of new user group
753 :param group_name: name of new user group
754 :type group_name: str
754 :type group_name: str
755 :param description: group description
755 :param description: group description
756 :type description: str
756 :type description: str
757 :param owner: owner of group.
757 :param owner: owner of group.
758 :type owner: Optional(str or int)
758 :type owner: Optional(str or int)
759 :param active: group is active
759 :param active: group is active
760 :type active: Optional(bool)
760 :type active: Optional(bool)
761
761
762 OUTPUT::
762 OUTPUT::
763
763
764 id : <id_given_in_input>
764 id : <id_given_in_input>
765 result : {
765 result : {
766 "msg": 'updated user group ID:<user group id> <user group name>',
766 "msg": 'updated user group ID:<user group id> <user group name>',
767 "user_group": <user_group_object>
767 "user_group": <user_group_object>
768 }
768 }
769 error : null
769 error : null
770
770
771 ERROR OUTPUT::
771 ERROR OUTPUT::
772
772
773 id : <id_given_in_input>
773 id : <id_given_in_input>
774 result : null
774 result : null
775 error : {
775 error : {
776 "failed to update user group `<user group name>`"
776 "failed to update user group `<user group name>`"
777 }
777 }
778
778
779 """
779 """
780 user_group = get_user_group_or_error(usergroupid)
780 user_group = get_user_group_or_error(usergroupid)
781 if not HasPermissionAny('hg.admin')():
781 if not HasPermissionAny('hg.admin')():
782 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
782 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
783 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
783 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
784
784
785 if owner is not None:
785 if owner is not None:
786 owner = get_user_or_error(owner)
786 owner = get_user_or_error(owner)
787
787
788 updates = {}
788 updates = {}
789 store_update(updates, group_name, 'users_group_name')
789 store_update(updates, group_name, 'users_group_name')
790 store_update(updates, description, 'user_group_description')
790 store_update(updates, description, 'user_group_description')
791 store_update(updates, owner, 'owner')
791 store_update(updates, owner, 'owner')
792 store_update(updates, active, 'users_group_active')
792 store_update(updates, active, 'users_group_active')
793 try:
793 try:
794 UserGroupModel().update(user_group, updates)
794 UserGroupModel().update(user_group, updates)
795 meta.Session().commit()
795 meta.Session().commit()
796 return dict(
796 return dict(
797 msg='updated user group ID:%s %s' % (user_group.users_group_id,
797 msg='updated user group ID:%s %s' % (user_group.users_group_id,
798 user_group.users_group_name),
798 user_group.users_group_name),
799 user_group=user_group.get_api_data()
799 user_group=user_group.get_api_data()
800 )
800 )
801 except Exception:
801 except Exception:
802 log.error(traceback.format_exc())
802 log.error(traceback.format_exc())
803 raise JSONRPCError('failed to update user group `%s`' % (usergroupid,))
803 raise JSONRPCError('failed to update user group `%s`' % (usergroupid,))
804
804
805 # permission check inside
805 # permission check inside
806 def delete_user_group(self, usergroupid):
806 def delete_user_group(self, usergroupid):
807 """
807 """
808 Delete given user group by user group id or name.
808 Delete given user group by user group id or name.
809 This command can be executed only using api_key
809 This command can be executed only using api_key
810 belonging to user with admin rights or an admin of given user group
810 belonging to user with admin rights or an admin of given user group
811
811
812 :param usergroupid:
812 :param usergroupid:
813 :type usergroupid: int
813 :type usergroupid: int
814
814
815 OUTPUT::
815 OUTPUT::
816
816
817 id : <id_given_in_input>
817 id : <id_given_in_input>
818 result : {
818 result : {
819 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
819 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
820 }
820 }
821 error : null
821 error : null
822
822
823 ERROR OUTPUT::
823 ERROR OUTPUT::
824
824
825 id : <id_given_in_input>
825 id : <id_given_in_input>
826 result : null
826 result : null
827 error : {
827 error : {
828 "failed to delete user group ID:<user_group_id> <user_group_name>"
828 "failed to delete user group ID:<user_group_id> <user_group_name>"
829 or
829 or
830 "RepoGroup assigned to <repo_groups_list>"
830 "RepoGroup assigned to <repo_groups_list>"
831 }
831 }
832
832
833 """
833 """
834 user_group = get_user_group_or_error(usergroupid)
834 user_group = get_user_group_or_error(usergroupid)
835 if not HasPermissionAny('hg.admin')():
835 if not HasPermissionAny('hg.admin')():
836 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
836 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
837 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
837 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
838
838
839 try:
839 try:
840 UserGroupModel().delete(user_group)
840 UserGroupModel().delete(user_group)
841 meta.Session().commit()
841 meta.Session().commit()
842 return dict(
842 return dict(
843 msg='deleted user group ID:%s %s' %
843 msg='deleted user group ID:%s %s' %
844 (user_group.users_group_id, user_group.users_group_name),
844 (user_group.users_group_id, user_group.users_group_name),
845 user_group=None
845 user_group=None
846 )
846 )
847 except UserGroupsAssignedException as e:
847 except UserGroupsAssignedException as e:
848 log.error(traceback.format_exc())
848 log.error(traceback.format_exc())
849 raise JSONRPCError(str(e))
849 raise JSONRPCError(str(e))
850 except Exception:
850 except Exception:
851 log.error(traceback.format_exc())
851 log.error(traceback.format_exc())
852 raise JSONRPCError('failed to delete user group ID:%s %s' %
852 raise JSONRPCError('failed to delete user group ID:%s %s' %
853 (user_group.users_group_id,
853 (user_group.users_group_id,
854 user_group.users_group_name)
854 user_group.users_group_name)
855 )
855 )
856
856
857 # permission check inside
857 # permission check inside
858 def add_user_to_user_group(self, usergroupid, userid):
858 def add_user_to_user_group(self, usergroupid, userid):
859 """
859 """
860 Adds a user to a user group. If user exists in that group success will be
860 Adds a user to a user group. If user exists in that group success will be
861 `false`. This command can be executed only using api_key
861 `false`. This command can be executed only using api_key
862 belonging to user with admin rights or an admin of given user group
862 belonging to user with admin rights or an admin of given user group
863
863
864 :param usergroupid:
864 :param usergroupid:
865 :type usergroupid: int
865 :type usergroupid: int
866 :param userid:
866 :param userid:
867 :type userid: int
867 :type userid: int
868
868
869 OUTPUT::
869 OUTPUT::
870
870
871 id : <id_given_in_input>
871 id : <id_given_in_input>
872 result : {
872 result : {
873 "success": True|False # depends on if member is in group
873 "success": True|False # depends on if member is in group
874 "msg": "added member `<username>` to user group `<groupname>` |
874 "msg": "added member `<username>` to user group `<groupname>` |
875 User is already in that group"
875 User is already in that group"
876
876
877 }
877 }
878 error : null
878 error : null
879
879
880 ERROR OUTPUT::
880 ERROR OUTPUT::
881
881
882 id : <id_given_in_input>
882 id : <id_given_in_input>
883 result : null
883 result : null
884 error : {
884 error : {
885 "failed to add member to user group `<user_group_name>`"
885 "failed to add member to user group `<user_group_name>`"
886 }
886 }
887
887
888 """
888 """
889 user = get_user_or_error(userid)
889 user = get_user_or_error(userid)
890 user_group = get_user_group_or_error(usergroupid)
890 user_group = get_user_group_or_error(usergroupid)
891 if not HasPermissionAny('hg.admin')():
891 if not HasPermissionAny('hg.admin')():
892 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
892 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
893 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
893 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
894
894
895 try:
895 try:
896 ugm = UserGroupModel().add_user_to_group(user_group, user)
896 ugm = UserGroupModel().add_user_to_group(user_group, user)
897 success = True if ugm is not True else False
897 success = True if ugm is not True else False
898 msg = 'added member `%s` to user group `%s`' % (
898 msg = 'added member `%s` to user group `%s`' % (
899 user.username, user_group.users_group_name
899 user.username, user_group.users_group_name
900 )
900 )
901 msg = msg if success else 'User is already in that group'
901 msg = msg if success else 'User is already in that group'
902 meta.Session().commit()
902 meta.Session().commit()
903
903
904 return dict(
904 return dict(
905 success=success,
905 success=success,
906 msg=msg
906 msg=msg
907 )
907 )
908 except Exception:
908 except Exception:
909 log.error(traceback.format_exc())
909 log.error(traceback.format_exc())
910 raise JSONRPCError(
910 raise JSONRPCError(
911 'failed to add member to user group `%s`' % (
911 'failed to add member to user group `%s`' % (
912 user_group.users_group_name,
912 user_group.users_group_name,
913 )
913 )
914 )
914 )
915
915
916 # permission check inside
916 # permission check inside
917 def remove_user_from_user_group(self, usergroupid, userid):
917 def remove_user_from_user_group(self, usergroupid, userid):
918 """
918 """
919 Removes a user from a user group. If user is not in given group success will
919 Removes a user from a user group. If user is not in given group success will
920 be `false`. This command can be executed only
920 be `false`. This command can be executed only
921 using api_key belonging to user with admin rights or an admin of given user group
921 using api_key belonging to user with admin rights or an admin of given user group
922
922
923 :param usergroupid:
923 :param usergroupid:
924 :param userid:
924 :param userid:
925
925
926
926
927 OUTPUT::
927 OUTPUT::
928
928
929 id : <id_given_in_input>
929 id : <id_given_in_input>
930 result: {
930 result: {
931 "success": True|False, # depends on if member is in group
931 "success": True|False, # depends on if member is in group
932 "msg": "removed member <username> from user group <groupname> |
932 "msg": "removed member <username> from user group <groupname> |
933 User wasn't in group"
933 User wasn't in group"
934 }
934 }
935 error: null
935 error: null
936
936
937 """
937 """
938 user = get_user_or_error(userid)
938 user = get_user_or_error(userid)
939 user_group = get_user_group_or_error(usergroupid)
939 user_group = get_user_group_or_error(usergroupid)
940 if not HasPermissionAny('hg.admin')():
940 if not HasPermissionAny('hg.admin')():
941 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
941 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
942 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
942 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
943
943
944 try:
944 try:
945 success = UserGroupModel().remove_user_from_group(user_group, user)
945 success = UserGroupModel().remove_user_from_group(user_group, user)
946 msg = 'removed member `%s` from user group `%s`' % (
946 msg = 'removed member `%s` from user group `%s`' % (
947 user.username, user_group.users_group_name
947 user.username, user_group.users_group_name
948 )
948 )
949 msg = msg if success else "User wasn't in group"
949 msg = msg if success else "User wasn't in group"
950 meta.Session().commit()
950 meta.Session().commit()
951 return dict(success=success, msg=msg)
951 return dict(success=success, msg=msg)
952 except Exception:
952 except Exception:
953 log.error(traceback.format_exc())
953 log.error(traceback.format_exc())
954 raise JSONRPCError(
954 raise JSONRPCError(
955 'failed to remove member from user group `%s`' % (
955 'failed to remove member from user group `%s`' % (
956 user_group.users_group_name,
956 user_group.users_group_name,
957 )
957 )
958 )
958 )
959
959
960 # permission check inside
960 # permission check inside
961 def get_repo(self, repoid,
961 def get_repo(self, repoid,
962 with_revision_names=False,
962 with_revision_names=False,
963 with_pullrequests=False):
963 with_pullrequests=False):
964 """
964 """
965 Gets an existing repository by it's name or repository_id. Members will return
965 Gets an existing repository by it's name or repository_id. Members will return
966 either users_group or user associated to that repository. This command can be
966 either users_group or user associated to that repository. This command can be
967 executed only using api_key belonging to user with admin
967 executed only using api_key belonging to user with admin
968 rights or regular user that have at least read access to repository.
968 rights or regular user that have at least read access to repository.
969
969
970 :param repoid: repository name or repository id
970 :param repoid: repository name or repository id
971 :type repoid: str or int
971 :type repoid: str or int
972
972
973 OUTPUT::
973 OUTPUT::
974
974
975 id : <id_given_in_input>
975 id : <id_given_in_input>
976 result : {
976 result : {
977 {
977 {
978 "repo_id" : "<repo_id>",
978 "repo_id" : "<repo_id>",
979 "repo_name" : "<reponame>"
979 "repo_name" : "<reponame>"
980 "repo_type" : "<repo_type>",
980 "repo_type" : "<repo_type>",
981 "clone_uri" : "<clone_uri>",
981 "clone_uri" : "<clone_uri>",
982 "enable_downloads": "<bool>",
982 "enable_downloads": "<bool>",
983 "enable_statistics": "<bool>",
983 "enable_statistics": "<bool>",
984 "private": "<bool>",
984 "private": "<bool>",
985 "created_on" : "<date_time_created>",
985 "created_on" : "<date_time_created>",
986 "description" : "<description>",
986 "description" : "<description>",
987 "landing_rev": "<landing_rev>",
987 "landing_rev": "<landing_rev>",
988 "last_changeset": {
988 "last_changeset": {
989 "author": "<full_author>",
989 "author": "<full_author>",
990 "date": "<date_time_of_commit>",
990 "date": "<date_time_of_commit>",
991 "message": "<commit_message>",
991 "message": "<commit_message>",
992 "raw_id": "<raw_id>",
992 "raw_id": "<raw_id>",
993 "revision": "<numeric_revision>",
993 "revision": "<numeric_revision>",
994 "short_id": "<short_id>"
994 "short_id": "<short_id>"
995 }
995 }
996 "owner": "<repo_owner>",
996 "owner": "<repo_owner>",
997 "fork_of": "<name_of_fork_parent>",
997 "fork_of": "<name_of_fork_parent>",
998 "members" : [
998 "members" : [
999 {
999 {
1000 "name": "<username>",
1000 "name": "<username>",
1001 "type" : "user",
1001 "type" : "user",
1002 "permission" : "repository.(read|write|admin)"
1002 "permission" : "repository.(read|write|admin)"
1003 },
1003 },
1004 …
1004 …
1005 {
1005 {
1006 "name": "<usergroup name>",
1006 "name": "<usergroup name>",
1007 "type" : "user_group",
1007 "type" : "user_group",
1008 "permission" : "usergroup.(read|write|admin)"
1008 "permission" : "usergroup.(read|write|admin)"
1009 },
1009 },
1010 …
1010 …
1011 ]
1011 ]
1012 "followers": [<user_obj>, ...],
1012 "followers": [<user_obj>, ...],
1013 <if with_revision_names == True>
1013 <if with_revision_names == True>
1014 "tags": {
1014 "tags": {
1015 "<tagname>": "<raw_id>",
1015 "<tagname>": "<raw_id>",
1016 ...
1016 ...
1017 },
1017 },
1018 "branches": {
1018 "branches": {
1019 "<branchname>": "<raw_id>",
1019 "<branchname>": "<raw_id>",
1020 ...
1020 ...
1021 },
1021 },
1022 "bookmarks": {
1022 "bookmarks": {
1023 "<bookmarkname>": "<raw_id>",
1023 "<bookmarkname>": "<raw_id>",
1024 ...
1024 ...
1025 },
1025 },
1026 }
1026 }
1027 }
1027 }
1028 error : null
1028 error : null
1029
1029
1030 """
1030 """
1031 repo = get_repo_or_error(repoid)
1031 repo = get_repo_or_error(repoid)
1032
1032
1033 if not HasPermissionAny('hg.admin')():
1033 if not HasPermissionAny('hg.admin')():
1034 if not HasRepoPermissionLevel('read')(repo.repo_name):
1034 if not HasRepoPermissionLevel('read')(repo.repo_name):
1035 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1035 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1036
1036
1037 members = []
1037 members = []
1038 for user in repo.repo_to_perm:
1038 for user in repo.repo_to_perm:
1039 perm = user.permission.permission_name
1039 perm = user.permission.permission_name
1040 user = user.user
1040 user = user.user
1041 user_data = {
1041 user_data = {
1042 'name': user.username,
1042 'name': user.username,
1043 'type': "user",
1043 'type': "user",
1044 'permission': perm
1044 'permission': perm
1045 }
1045 }
1046 members.append(user_data)
1046 members.append(user_data)
1047
1047
1048 for user_group in repo.users_group_to_perm:
1048 for user_group in repo.users_group_to_perm:
1049 perm = user_group.permission.permission_name
1049 perm = user_group.permission.permission_name
1050 user_group = user_group.users_group
1050 user_group = user_group.users_group
1051 user_group_data = {
1051 user_group_data = {
1052 'name': user_group.users_group_name,
1052 'name': user_group.users_group_name,
1053 'type': "user_group",
1053 'type': "user_group",
1054 'permission': perm
1054 'permission': perm
1055 }
1055 }
1056 members.append(user_group_data)
1056 members.append(user_group_data)
1057
1057
1058 followers = [
1058 followers = [
1059 uf.user.get_api_data()
1059 uf.user.get_api_data()
1060 for uf in repo.followers
1060 for uf in repo.followers
1061 ]
1061 ]
1062
1062
1063 data = repo.get_api_data(with_revision_names=with_revision_names,
1063 data = repo.get_api_data(with_revision_names=with_revision_names,
1064 with_pullrequests=with_pullrequests)
1064 with_pullrequests=with_pullrequests)
1065 data['members'] = members
1065 data['members'] = members
1066 data['followers'] = followers
1066 data['followers'] = followers
1067 return data
1067 return data
1068
1068
1069 # permission check inside
1069 # permission check inside
1070 def get_repos(self):
1070 def get_repos(self):
1071 """
1071 """
1072 Lists all existing repositories. This command can be executed only using
1072 Lists all existing repositories. This command can be executed only using
1073 api_key belonging to user with admin rights or regular user that have
1073 api_key belonging to user with admin rights or regular user that have
1074 admin, write or read access to repository.
1074 admin, write or read access to repository.
1075
1075
1076
1076
1077 OUTPUT::
1077 OUTPUT::
1078
1078
1079 id : <id_given_in_input>
1079 id : <id_given_in_input>
1080 result: [
1080 result: [
1081 {
1081 {
1082 "repo_id" : "<repo_id>",
1082 "repo_id" : "<repo_id>",
1083 "repo_name" : "<reponame>"
1083 "repo_name" : "<reponame>"
1084 "repo_type" : "<repo_type>",
1084 "repo_type" : "<repo_type>",
1085 "clone_uri" : "<clone_uri>",
1085 "clone_uri" : "<clone_uri>",
1086 "private": : "<bool>",
1086 "private": : "<bool>",
1087 "created_on" : "<datetimecreated>",
1087 "created_on" : "<datetimecreated>",
1088 "description" : "<description>",
1088 "description" : "<description>",
1089 "landing_rev": "<landing_rev>",
1089 "landing_rev": "<landing_rev>",
1090 "owner": "<repo_owner>",
1090 "owner": "<repo_owner>",
1091 "fork_of": "<name_of_fork_parent>",
1091 "fork_of": "<name_of_fork_parent>",
1092 "enable_downloads": "<bool>",
1092 "enable_downloads": "<bool>",
1093 "enable_statistics": "<bool>",
1093 "enable_statistics": "<bool>",
1094 },
1094 },
1095 …
1095 …
1096 ]
1096 ]
1097 error: null
1097 error: null
1098 """
1098 """
1099 if not HasPermissionAny('hg.admin')():
1099 if not HasPermissionAny('hg.admin')():
1100 repos = request.authuser.get_all_user_repos()
1100 repos = request.authuser.get_all_user_repos()
1101 else:
1101 else:
1102 repos = db.Repository.query()
1102 repos = db.Repository.query()
1103
1103
1104 return [
1104 return [
1105 repo.get_api_data()
1105 repo.get_api_data()
1106 for repo in repos
1106 for repo in repos
1107 ]
1107 ]
1108
1108
1109 # permission check inside
1109 # permission check inside
1110 def get_repo_nodes(self, repoid, revision, root_path,
1110 def get_repo_nodes(self, repoid, revision, root_path,
1111 ret_type='all'):
1111 ret_type='all'):
1112 """
1112 """
1113 returns a list of nodes and it's children in a flat list for a given path
1113 returns a list of nodes and it's children in a flat list for a given path
1114 at given revision. It's possible to specify ret_type to show only `files` or
1114 at given revision. It's possible to specify ret_type to show only `files` or
1115 `dirs`. This command can be executed only using api_key belonging to
1115 `dirs`. This command can be executed only using api_key belonging to
1116 user with admin rights or regular user that have at least read access to repository.
1116 user with admin rights or regular user that have at least read access to repository.
1117
1117
1118 :param repoid: repository name or repository id
1118 :param repoid: repository name or repository id
1119 :type repoid: str or int
1119 :type repoid: str or int
1120 :param revision: revision for which listing should be done
1120 :param revision: revision for which listing should be done
1121 :type revision: str
1121 :type revision: str
1122 :param root_path: path from which start displaying
1122 :param root_path: path from which start displaying
1123 :type root_path: str
1123 :type root_path: str
1124 :param ret_type: return type 'all|files|dirs' nodes
1124 :param ret_type: return type 'all|files|dirs' nodes
1125 :type ret_type: Optional(str)
1125 :type ret_type: Optional(str)
1126
1126
1127
1127
1128 OUTPUT::
1128 OUTPUT::
1129
1129
1130 id : <id_given_in_input>
1130 id : <id_given_in_input>
1131 result: [
1131 result: [
1132 {
1132 {
1133 "name" : "<name>"
1133 "name" : "<name>"
1134 "type" : "<type>",
1134 "type" : "<type>",
1135 },
1135 },
1136 …
1136 …
1137 ]
1137 ]
1138 error: null
1138 error: null
1139 """
1139 """
1140 repo = get_repo_or_error(repoid)
1140 repo = get_repo_or_error(repoid)
1141
1141
1142 if not HasPermissionAny('hg.admin')():
1142 if not HasPermissionAny('hg.admin')():
1143 if not HasRepoPermissionLevel('read')(repo.repo_name):
1143 if not HasRepoPermissionLevel('read')(repo.repo_name):
1144 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1144 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1145
1145
1146 ret_type = ret_type
1147 _map = {}
1146 _map = {}
1148 try:
1147 try:
1149 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
1148 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
1150 flat=False)
1149 flat=False)
1151 _map = {
1150 _map = {
1152 'all': _d + _f,
1151 'all': _d + _f,
1153 'files': _f,
1152 'files': _f,
1154 'dirs': _d,
1153 'dirs': _d,
1155 }
1154 }
1156 return _map[ret_type]
1155 return _map[ret_type]
1157 except KeyError:
1156 except KeyError:
1158 raise JSONRPCError('ret_type must be one of %s'
1157 raise JSONRPCError('ret_type must be one of %s'
1159 % (','.join(sorted(_map))))
1158 % (','.join(sorted(_map))))
1160 except Exception:
1159 except Exception:
1161 log.error(traceback.format_exc())
1160 log.error(traceback.format_exc())
1162 raise JSONRPCError(
1161 raise JSONRPCError(
1163 'failed to get repo: `%s` nodes' % repo.repo_name
1162 'failed to get repo: `%s` nodes' % repo.repo_name
1164 )
1163 )
1165
1164
1166 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
1165 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
1167 def create_repo(self, repo_name, owner=None,
1166 def create_repo(self, repo_name, owner=None,
1168 repo_type=None, description='',
1167 repo_type=None, description='',
1169 private=False, clone_uri=None,
1168 private=False, clone_uri=None,
1170 landing_rev='rev:tip',
1169 landing_rev='rev:tip',
1171 enable_statistics=None,
1170 enable_statistics=None,
1172 enable_downloads=None,
1171 enable_downloads=None,
1173 copy_permissions=False):
1172 copy_permissions=False):
1174 """
1173 """
1175 Creates a repository. The repository name contains the full path, but the
1174 Creates a repository. The repository name contains the full path, but the
1176 parent repository group must exist. For example "foo/bar/baz" require the groups
1175 parent repository group must exist. For example "foo/bar/baz" require the groups
1177 "foo" and "bar" (with "foo" as parent), and create "baz" repository with
1176 "foo" and "bar" (with "foo" as parent), and create "baz" repository with
1178 "bar" as group. This command can be executed only using api_key
1177 "bar" as group. This command can be executed only using api_key
1179 belonging to user with admin rights or regular user that have create
1178 belonging to user with admin rights or regular user that have create
1180 repository permission. Regular users cannot specify owner parameter
1179 repository permission. Regular users cannot specify owner parameter
1181
1180
1182 :param repo_name: repository name
1181 :param repo_name: repository name
1183 :type repo_name: str
1182 :type repo_name: str
1184 :param owner: user_id or username
1183 :param owner: user_id or username
1185 :type owner: Optional(str)
1184 :type owner: Optional(str)
1186 :param repo_type: 'hg' or 'git'
1185 :param repo_type: 'hg' or 'git'
1187 :type repo_type: Optional(str)
1186 :type repo_type: Optional(str)
1188 :param description: repository description
1187 :param description: repository description
1189 :type description: Optional(str)
1188 :type description: Optional(str)
1190 :param private:
1189 :param private:
1191 :type private: bool
1190 :type private: bool
1192 :param clone_uri:
1191 :param clone_uri:
1193 :type clone_uri: str
1192 :type clone_uri: str
1194 :param landing_rev: <rev_type>:<rev>
1193 :param landing_rev: <rev_type>:<rev>
1195 :type landing_rev: str
1194 :type landing_rev: str
1196 :param enable_downloads:
1195 :param enable_downloads:
1197 :type enable_downloads: bool
1196 :type enable_downloads: bool
1198 :param enable_statistics:
1197 :param enable_statistics:
1199 :type enable_statistics: bool
1198 :type enable_statistics: bool
1200 :param copy_permissions: Copy permission from group that repository is
1199 :param copy_permissions: Copy permission from group that repository is
1201 being created.
1200 being created.
1202 :type copy_permissions: bool
1201 :type copy_permissions: bool
1203
1202
1204 OUTPUT::
1203 OUTPUT::
1205
1204
1206 id : <id_given_in_input>
1205 id : <id_given_in_input>
1207 result: {
1206 result: {
1208 "msg": "Created new repository `<reponame>`",
1207 "msg": "Created new repository `<reponame>`",
1209 "success": true,
1208 "success": true,
1210 "task": "<celery task id or None if done sync>"
1209 "task": "<celery task id or None if done sync>"
1211 }
1210 }
1212 error: null
1211 error: null
1213
1212
1214 ERROR OUTPUT::
1213 ERROR OUTPUT::
1215
1214
1216 id : <id_given_in_input>
1215 id : <id_given_in_input>
1217 result : null
1216 result : null
1218 error : {
1217 error : {
1219 'failed to create repository `<repo_name>`
1218 'failed to create repository `<repo_name>`
1220 }
1219 }
1221
1220
1222 """
1221 """
1223 if not HasPermissionAny('hg.admin')():
1222 if not HasPermissionAny('hg.admin')():
1224 if owner is not None:
1223 if owner is not None:
1225 # forbid setting owner for non-admins
1224 # forbid setting owner for non-admins
1226 raise JSONRPCError(
1225 raise JSONRPCError(
1227 'Only Kallithea admin can specify `owner` param'
1226 'Only Kallithea admin can specify `owner` param'
1228 )
1227 )
1229 if owner is None:
1228 if owner is None:
1230 owner = request.authuser.user_id
1229 owner = request.authuser.user_id
1231
1230
1232 owner = get_user_or_error(owner)
1231 owner = get_user_or_error(owner)
1233
1232
1234 if RepoModel().get_by_repo_name(repo_name):
1233 if RepoModel().get_by_repo_name(repo_name):
1235 raise JSONRPCError("repo `%s` already exist" % repo_name)
1234 raise JSONRPCError("repo `%s` already exist" % repo_name)
1236
1235
1237 defs = db.Setting.get_default_repo_settings(strip_prefix=True)
1236 defs = db.Setting.get_default_repo_settings(strip_prefix=True)
1238 if private is None:
1237 if private is None:
1239 private = defs.get('repo_private') or False
1238 private = defs.get('repo_private') or False
1240 if repo_type is None:
1239 if repo_type is None:
1241 repo_type = defs.get('repo_type')
1240 repo_type = defs.get('repo_type')
1242 if enable_statistics is None:
1241 if enable_statistics is None:
1243 enable_statistics = defs.get('repo_enable_statistics')
1242 enable_statistics = defs.get('repo_enable_statistics')
1244 if enable_downloads is None:
1243 if enable_downloads is None:
1245 enable_downloads = defs.get('repo_enable_downloads')
1244 enable_downloads = defs.get('repo_enable_downloads')
1246
1245
1247 try:
1246 try:
1248 repo_name_parts = repo_name.split('/')
1247 repo_name_parts = repo_name.split('/')
1249 repo_group = None
1248 repo_group = None
1250 if len(repo_name_parts) > 1:
1249 if len(repo_name_parts) > 1:
1251 group_name = '/'.join(repo_name_parts[:-1])
1250 group_name = '/'.join(repo_name_parts[:-1])
1252 repo_group = db.RepoGroup.get_by_group_name(group_name)
1251 repo_group = db.RepoGroup.get_by_group_name(group_name)
1253 if repo_group is None:
1252 if repo_group is None:
1254 raise JSONRPCError("repo group `%s` not found" % group_name)
1253 raise JSONRPCError("repo group `%s` not found" % group_name)
1255 data = dict(
1254 data = dict(
1256 repo_name=repo_name_parts[-1],
1255 repo_name=repo_name_parts[-1],
1257 repo_name_full=repo_name,
1256 repo_name_full=repo_name,
1258 repo_type=repo_type,
1257 repo_type=repo_type,
1259 repo_description=description,
1258 repo_description=description,
1260 owner=owner,
1259 owner=owner,
1261 repo_private=private,
1260 repo_private=private,
1262 clone_uri=clone_uri,
1261 clone_uri=clone_uri,
1263 repo_group=repo_group,
1262 repo_group=repo_group,
1264 repo_landing_rev=landing_rev,
1263 repo_landing_rev=landing_rev,
1265 enable_statistics=enable_statistics,
1264 enable_statistics=enable_statistics,
1266 enable_downloads=enable_downloads,
1265 enable_downloads=enable_downloads,
1267 repo_copy_permissions=copy_permissions,
1266 repo_copy_permissions=copy_permissions,
1268 )
1267 )
1269
1268
1270 task = RepoModel().create(form_data=data, cur_user=owner)
1269 task = RepoModel().create(form_data=data, cur_user=owner)
1271 task_id = task.task_id
1270 task_id = task.task_id
1272 # no commit, it's done in RepoModel, or async via celery
1271 # no commit, it's done in RepoModel, or async via celery
1273 return dict(
1272 return dict(
1274 msg="Created new repository `%s`" % (repo_name,),
1273 msg="Created new repository `%s`" % (repo_name,),
1275 success=True, # cannot return the repo data here since fork
1274 success=True, # cannot return the repo data here since fork
1276 # can be done async
1275 # can be done async
1277 task=task_id
1276 task=task_id
1278 )
1277 )
1279 except Exception:
1278 except Exception:
1280 log.error(traceback.format_exc())
1279 log.error(traceback.format_exc())
1281 raise JSONRPCError(
1280 raise JSONRPCError(
1282 'failed to create repository `%s`' % (repo_name,))
1281 'failed to create repository `%s`' % (repo_name,))
1283
1282
1284 # permission check inside
1283 # permission check inside
1285 def update_repo(self, repoid, name=None,
1284 def update_repo(self, repoid, name=None,
1286 owner=None,
1285 owner=None,
1287 group=None,
1286 group=None,
1288 description=None, private=None,
1287 description=None, private=None,
1289 clone_uri=None, landing_rev=None,
1288 clone_uri=None, landing_rev=None,
1290 enable_statistics=None,
1289 enable_statistics=None,
1291 enable_downloads=None):
1290 enable_downloads=None):
1292
1291
1293 """
1292 """
1294 Updates repo
1293 Updates repo
1295
1294
1296 :param repoid: repository name or repository id
1295 :param repoid: repository name or repository id
1297 :type repoid: str or int
1296 :type repoid: str or int
1298 :param name:
1297 :param name:
1299 :param owner:
1298 :param owner:
1300 :param group:
1299 :param group:
1301 :param description:
1300 :param description:
1302 :param private:
1301 :param private:
1303 :param clone_uri:
1302 :param clone_uri:
1304 :param landing_rev:
1303 :param landing_rev:
1305 :param enable_statistics:
1304 :param enable_statistics:
1306 :param enable_downloads:
1305 :param enable_downloads:
1307 """
1306 """
1308 repo = get_repo_or_error(repoid)
1307 repo = get_repo_or_error(repoid)
1309 if not HasPermissionAny('hg.admin')():
1308 if not HasPermissionAny('hg.admin')():
1310 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1309 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1311 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1310 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1312
1311
1313 if (name != repo.repo_name and
1312 if (name != repo.repo_name and
1314 not HasPermissionAny('hg.create.repository')()
1313 not HasPermissionAny('hg.create.repository')()
1315 ):
1314 ):
1316 raise JSONRPCError('no permission to create (or move) repositories')
1315 raise JSONRPCError('no permission to create (or move) repositories')
1317
1316
1318 if owner is not None:
1317 if owner is not None:
1319 # forbid setting owner for non-admins
1318 # forbid setting owner for non-admins
1320 raise JSONRPCError(
1319 raise JSONRPCError(
1321 'Only Kallithea admin can specify `owner` param'
1320 'Only Kallithea admin can specify `owner` param'
1322 )
1321 )
1323
1322
1324 updates = {}
1323 updates = {}
1325 repo_group = group
1324 repo_group = group
1326 if repo_group is not None:
1325 if repo_group is not None:
1327 repo_group = get_repo_group_or_error(repo_group)
1326 repo_group = get_repo_group_or_error(repo_group)
1328 repo_group = repo_group.group_id
1327 repo_group = repo_group.group_id
1329 try:
1328 try:
1330 store_update(updates, name, 'repo_name')
1329 store_update(updates, name, 'repo_name')
1331 store_update(updates, repo_group, 'repo_group')
1330 store_update(updates, repo_group, 'repo_group')
1332 store_update(updates, owner, 'owner')
1331 store_update(updates, owner, 'owner')
1333 store_update(updates, description, 'repo_description')
1332 store_update(updates, description, 'repo_description')
1334 store_update(updates, private, 'repo_private')
1333 store_update(updates, private, 'repo_private')
1335 store_update(updates, clone_uri, 'clone_uri')
1334 store_update(updates, clone_uri, 'clone_uri')
1336 store_update(updates, landing_rev, 'repo_landing_rev')
1335 store_update(updates, landing_rev, 'repo_landing_rev')
1337 store_update(updates, enable_statistics, 'repo_enable_statistics')
1336 store_update(updates, enable_statistics, 'repo_enable_statistics')
1338 store_update(updates, enable_downloads, 'repo_enable_downloads')
1337 store_update(updates, enable_downloads, 'repo_enable_downloads')
1339
1338
1340 RepoModel().update(repo, **updates)
1339 RepoModel().update(repo, **updates)
1341 meta.Session().commit()
1340 meta.Session().commit()
1342 return dict(
1341 return dict(
1343 msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1342 msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1344 repository=repo.get_api_data()
1343 repository=repo.get_api_data()
1345 )
1344 )
1346 except Exception:
1345 except Exception:
1347 log.error(traceback.format_exc())
1346 log.error(traceback.format_exc())
1348 raise JSONRPCError('failed to update repo `%s`' % repoid)
1347 raise JSONRPCError('failed to update repo `%s`' % repoid)
1349
1348
1350 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
1349 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
1351 def fork_repo(self, repoid, fork_name,
1350 def fork_repo(self, repoid, fork_name,
1352 owner=None,
1351 owner=None,
1353 description='', copy_permissions=False,
1352 description='', copy_permissions=False,
1354 private=False, landing_rev='rev:tip'):
1353 private=False, landing_rev='rev:tip'):
1355 """
1354 """
1356 Creates a fork of given repo. In case of using celery this will
1355 Creates a fork of given repo. In case of using celery this will
1357 immediately return success message, while fork is going to be created
1356 immediately return success message, while fork is going to be created
1358 asynchronous. This command can be executed only using api_key belonging to
1357 asynchronous. This command can be executed only using api_key belonging to
1359 user with admin rights or regular user that have fork permission, and at least
1358 user with admin rights or regular user that have fork permission, and at least
1360 read access to forking repository. Regular users cannot specify owner parameter.
1359 read access to forking repository. Regular users cannot specify owner parameter.
1361
1360
1362 :param repoid: repository name or repository id
1361 :param repoid: repository name or repository id
1363 :type repoid: str or int
1362 :type repoid: str or int
1364 :param fork_name:
1363 :param fork_name:
1365 :param owner:
1364 :param owner:
1366 :param description:
1365 :param description:
1367 :param copy_permissions:
1366 :param copy_permissions:
1368 :param private:
1367 :param private:
1369 :param landing_rev:
1368 :param landing_rev:
1370
1369
1371 INPUT::
1370 INPUT::
1372
1371
1373 id : <id_for_response>
1372 id : <id_for_response>
1374 api_key : "<api_key>"
1373 api_key : "<api_key>"
1375 args: {
1374 args: {
1376 "repoid" : "<reponame or repo_id>",
1375 "repoid" : "<reponame or repo_id>",
1377 "fork_name": "<forkname>",
1376 "fork_name": "<forkname>",
1378 "owner": "<username or user_id = Optional(=apiuser)>",
1377 "owner": "<username or user_id = Optional(=apiuser)>",
1379 "description": "<description>",
1378 "description": "<description>",
1380 "copy_permissions": "<bool>",
1379 "copy_permissions": "<bool>",
1381 "private": "<bool>",
1380 "private": "<bool>",
1382 "landing_rev": "<landing_rev>"
1381 "landing_rev": "<landing_rev>"
1383 }
1382 }
1384
1383
1385 OUTPUT::
1384 OUTPUT::
1386
1385
1387 id : <id_given_in_input>
1386 id : <id_given_in_input>
1388 result: {
1387 result: {
1389 "msg": "Created fork of `<reponame>` as `<forkname>`",
1388 "msg": "Created fork of `<reponame>` as `<forkname>`",
1390 "success": true,
1389 "success": true,
1391 "task": "<celery task id or None if done sync>"
1390 "task": "<celery task id or None if done sync>"
1392 }
1391 }
1393 error: null
1392 error: null
1394
1393
1395 """
1394 """
1396 repo = get_repo_or_error(repoid)
1395 repo = get_repo_or_error(repoid)
1397 repo_name = repo.repo_name
1396 repo_name = repo.repo_name
1398
1397
1399 _repo = RepoModel().get_by_repo_name(fork_name)
1398 _repo = RepoModel().get_by_repo_name(fork_name)
1400 if _repo:
1399 if _repo:
1401 type_ = 'fork' if _repo.fork else 'repo'
1400 type_ = 'fork' if _repo.fork else 'repo'
1402 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1401 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1403
1402
1404 if HasPermissionAny('hg.admin')():
1403 if HasPermissionAny('hg.admin')():
1405 pass
1404 pass
1406 elif HasRepoPermissionLevel('read')(repo.repo_name):
1405 elif HasRepoPermissionLevel('read')(repo.repo_name):
1407 if owner is not None:
1406 if owner is not None:
1408 # forbid setting owner for non-admins
1407 # forbid setting owner for non-admins
1409 raise JSONRPCError(
1408 raise JSONRPCError(
1410 'Only Kallithea admin can specify `owner` param'
1409 'Only Kallithea admin can specify `owner` param'
1411 )
1410 )
1412
1411
1413 if not HasPermissionAny('hg.create.repository')():
1412 if not HasPermissionAny('hg.create.repository')():
1414 raise JSONRPCError('no permission to create repositories')
1413 raise JSONRPCError('no permission to create repositories')
1415 else:
1414 else:
1416 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1415 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1417
1416
1418 if owner is None:
1417 if owner is None:
1419 owner = request.authuser.user_id
1418 owner = request.authuser.user_id
1420
1419
1421 owner = get_user_or_error(owner)
1420 owner = get_user_or_error(owner)
1422
1421
1423 try:
1422 try:
1424 fork_name_parts = fork_name.split('/')
1423 fork_name_parts = fork_name.split('/')
1425 repo_group = None
1424 repo_group = None
1426 if len(fork_name_parts) > 1:
1425 if len(fork_name_parts) > 1:
1427 group_name = '/'.join(fork_name_parts[:-1])
1426 group_name = '/'.join(fork_name_parts[:-1])
1428 repo_group = db.RepoGroup.get_by_group_name(group_name)
1427 repo_group = db.RepoGroup.get_by_group_name(group_name)
1429 if repo_group is None:
1428 if repo_group is None:
1430 raise JSONRPCError("repo group `%s` not found" % group_name)
1429 raise JSONRPCError("repo group `%s` not found" % group_name)
1431
1430
1432 form_data = dict(
1431 form_data = dict(
1433 repo_name=fork_name_parts[-1],
1432 repo_name=fork_name_parts[-1],
1434 repo_name_full=fork_name,
1433 repo_name_full=fork_name,
1435 repo_group=repo_group,
1434 repo_group=repo_group,
1436 repo_type=repo.repo_type,
1435 repo_type=repo.repo_type,
1437 description=description,
1436 description=description,
1438 private=private,
1437 private=private,
1439 copy_permissions=copy_permissions,
1438 copy_permissions=copy_permissions,
1440 landing_rev=landing_rev,
1439 landing_rev=landing_rev,
1441 update_after_clone=False,
1440 update_after_clone=False,
1442 fork_parent_id=repo.repo_id,
1441 fork_parent_id=repo.repo_id,
1443 )
1442 )
1444 task = RepoModel().create_fork(form_data, cur_user=owner)
1443 task = RepoModel().create_fork(form_data, cur_user=owner)
1445 # no commit, it's done in RepoModel, or async via celery
1444 # no commit, it's done in RepoModel, or async via celery
1446 task_id = task.task_id
1445 task_id = task.task_id
1447 return dict(
1446 return dict(
1448 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
1447 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
1449 fork_name),
1448 fork_name),
1450 success=True, # cannot return the repo data here since fork
1449 success=True, # cannot return the repo data here since fork
1451 # can be done async
1450 # can be done async
1452 task=task_id
1451 task=task_id
1453 )
1452 )
1454 except Exception:
1453 except Exception:
1455 log.error(traceback.format_exc())
1454 log.error(traceback.format_exc())
1456 raise JSONRPCError(
1455 raise JSONRPCError(
1457 'failed to fork repository `%s` as `%s`' % (repo_name,
1456 'failed to fork repository `%s` as `%s`' % (repo_name,
1458 fork_name)
1457 fork_name)
1459 )
1458 )
1460
1459
1461 # permission check inside
1460 # permission check inside
1462 def delete_repo(self, repoid, forks=''):
1461 def delete_repo(self, repoid, forks=''):
1463 """
1462 """
1464 Deletes a repository. This command can be executed only using api_key belonging
1463 Deletes a repository. This command can be executed only using api_key belonging
1465 to user with admin rights or regular user that have admin access to repository.
1464 to user with admin rights or regular user that have admin access to repository.
1466 When `forks` param is set it's possible to detach or delete forks of deleting
1465 When `forks` param is set it's possible to detach or delete forks of deleting
1467 repository
1466 repository
1468
1467
1469 :param repoid: repository name or repository id
1468 :param repoid: repository name or repository id
1470 :type repoid: str or int
1469 :type repoid: str or int
1471 :param forks: `detach` or `delete`, what do do with attached forks for repo
1470 :param forks: `detach` or `delete`, what do do with attached forks for repo
1472 :type forks: Optional(str)
1471 :type forks: Optional(str)
1473
1472
1474 OUTPUT::
1473 OUTPUT::
1475
1474
1476 id : <id_given_in_input>
1475 id : <id_given_in_input>
1477 result: {
1476 result: {
1478 "msg": "Deleted repository `<reponame>`",
1477 "msg": "Deleted repository `<reponame>`",
1479 "success": true
1478 "success": true
1480 }
1479 }
1481 error: null
1480 error: null
1482
1481
1483 """
1482 """
1484 repo = get_repo_or_error(repoid)
1483 repo = get_repo_or_error(repoid)
1485
1484
1486 if not HasPermissionAny('hg.admin')():
1485 if not HasPermissionAny('hg.admin')():
1487 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1486 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1488 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1487 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1489
1488
1490 try:
1489 try:
1491 handle_forks = forks
1490 handle_forks = forks
1492 _forks_msg = ''
1491 _forks_msg = ''
1493 _forks = [f for f in repo.forks]
1492 _forks = [f for f in repo.forks]
1494 if handle_forks == 'detach':
1493 if handle_forks == 'detach':
1495 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1494 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1496 elif handle_forks == 'delete':
1495 elif handle_forks == 'delete':
1497 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1496 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1498 elif _forks:
1497 elif _forks:
1499 raise JSONRPCError(
1498 raise JSONRPCError(
1500 'Cannot delete `%s` it still contains attached forks' %
1499 'Cannot delete `%s` it still contains attached forks' %
1501 (repo.repo_name,)
1500 (repo.repo_name,)
1502 )
1501 )
1503
1502
1504 RepoModel().delete(repo, forks=forks)
1503 RepoModel().delete(repo, forks=forks)
1505 meta.Session().commit()
1504 meta.Session().commit()
1506 return dict(
1505 return dict(
1507 msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
1506 msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
1508 success=True
1507 success=True
1509 )
1508 )
1510 except Exception:
1509 except Exception:
1511 log.error(traceback.format_exc())
1510 log.error(traceback.format_exc())
1512 raise JSONRPCError(
1511 raise JSONRPCError(
1513 'failed to delete repository `%s`' % (repo.repo_name,)
1512 'failed to delete repository `%s`' % (repo.repo_name,)
1514 )
1513 )
1515
1514
1516 @HasPermissionAnyDecorator('hg.admin')
1515 @HasPermissionAnyDecorator('hg.admin')
1517 def grant_user_permission(self, repoid, userid, perm):
1516 def grant_user_permission(self, repoid, userid, perm):
1518 """
1517 """
1519 Grant permission for user on given repository, or update existing one
1518 Grant permission for user on given repository, or update existing one
1520 if found. This command can be executed only using api_key belonging to user
1519 if found. This command can be executed only using api_key belonging to user
1521 with admin rights.
1520 with admin rights.
1522
1521
1523 :param repoid: repository name or repository id
1522 :param repoid: repository name or repository id
1524 :type repoid: str or int
1523 :type repoid: str or int
1525 :param userid:
1524 :param userid:
1526 :param perm: (repository.(none|read|write|admin))
1525 :param perm: (repository.(none|read|write|admin))
1527 :type perm: str
1526 :type perm: str
1528
1527
1529 OUTPUT::
1528 OUTPUT::
1530
1529
1531 id : <id_given_in_input>
1530 id : <id_given_in_input>
1532 result: {
1531 result: {
1533 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1532 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1534 "success": true
1533 "success": true
1535 }
1534 }
1536 error: null
1535 error: null
1537 """
1536 """
1538 repo = get_repo_or_error(repoid)
1537 repo = get_repo_or_error(repoid)
1539 user = get_user_or_error(userid)
1538 user = get_user_or_error(userid)
1540 perm = get_perm_or_error(perm)
1539 perm = get_perm_or_error(perm)
1541
1540
1542 try:
1541 try:
1543
1542
1544 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1543 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1545
1544
1546 meta.Session().commit()
1545 meta.Session().commit()
1547 return dict(
1546 return dict(
1548 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1547 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1549 perm.permission_name, user.username, repo.repo_name
1548 perm.permission_name, user.username, repo.repo_name
1550 ),
1549 ),
1551 success=True
1550 success=True
1552 )
1551 )
1553 except Exception:
1552 except Exception:
1554 log.error(traceback.format_exc())
1553 log.error(traceback.format_exc())
1555 raise JSONRPCError(
1554 raise JSONRPCError(
1556 'failed to edit permission for user: `%s` in repo: `%s`' % (
1555 'failed to edit permission for user: `%s` in repo: `%s`' % (
1557 userid, repoid
1556 userid, repoid
1558 )
1557 )
1559 )
1558 )
1560
1559
1561 @HasPermissionAnyDecorator('hg.admin')
1560 @HasPermissionAnyDecorator('hg.admin')
1562 def revoke_user_permission(self, repoid, userid):
1561 def revoke_user_permission(self, repoid, userid):
1563 """
1562 """
1564 Revoke permission for user on given repository. This command can be executed
1563 Revoke permission for user on given repository. This command can be executed
1565 only using api_key belonging to user with admin rights.
1564 only using api_key belonging to user with admin rights.
1566
1565
1567 :param repoid: repository name or repository id
1566 :param repoid: repository name or repository id
1568 :type repoid: str or int
1567 :type repoid: str or int
1569 :param userid:
1568 :param userid:
1570
1569
1571 OUTPUT::
1570 OUTPUT::
1572
1571
1573 id : <id_given_in_input>
1572 id : <id_given_in_input>
1574 result: {
1573 result: {
1575 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1574 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1576 "success": true
1575 "success": true
1577 }
1576 }
1578 error: null
1577 error: null
1579
1578
1580 """
1579 """
1581
1580
1582 repo = get_repo_or_error(repoid)
1581 repo = get_repo_or_error(repoid)
1583 user = get_user_or_error(userid)
1582 user = get_user_or_error(userid)
1584 try:
1583 try:
1585 RepoModel().revoke_user_permission(repo=repo, user=user)
1584 RepoModel().revoke_user_permission(repo=repo, user=user)
1586 meta.Session().commit()
1585 meta.Session().commit()
1587 return dict(
1586 return dict(
1588 msg='Revoked perm for user: `%s` in repo: `%s`' % (
1587 msg='Revoked perm for user: `%s` in repo: `%s`' % (
1589 user.username, repo.repo_name
1588 user.username, repo.repo_name
1590 ),
1589 ),
1591 success=True
1590 success=True
1592 )
1591 )
1593 except Exception:
1592 except Exception:
1594 log.error(traceback.format_exc())
1593 log.error(traceback.format_exc())
1595 raise JSONRPCError(
1594 raise JSONRPCError(
1596 'failed to edit permission for user: `%s` in repo: `%s`' % (
1595 'failed to edit permission for user: `%s` in repo: `%s`' % (
1597 userid, repoid
1596 userid, repoid
1598 )
1597 )
1599 )
1598 )
1600
1599
1601 # permission check inside
1600 # permission check inside
1602 def grant_user_group_permission(self, repoid, usergroupid, perm):
1601 def grant_user_group_permission(self, repoid, usergroupid, perm):
1603 """
1602 """
1604 Grant permission for user group on given repository, or update
1603 Grant permission for user group on given repository, or update
1605 existing one if found. This command can be executed only using
1604 existing one if found. This command can be executed only using
1606 api_key belonging to user with admin rights.
1605 api_key belonging to user with admin rights.
1607
1606
1608 :param repoid: repository name or repository id
1607 :param repoid: repository name or repository id
1609 :type repoid: str or int
1608 :type repoid: str or int
1610 :param usergroupid: id of usergroup
1609 :param usergroupid: id of usergroup
1611 :type usergroupid: str or int
1610 :type usergroupid: str or int
1612 :param perm: (repository.(none|read|write|admin))
1611 :param perm: (repository.(none|read|write|admin))
1613 :type perm: str
1612 :type perm: str
1614
1613
1615 OUTPUT::
1614 OUTPUT::
1616
1615
1617 id : <id_given_in_input>
1616 id : <id_given_in_input>
1618 result : {
1617 result : {
1619 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1618 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1620 "success": true
1619 "success": true
1621
1620
1622 }
1621 }
1623 error : null
1622 error : null
1624
1623
1625 ERROR OUTPUT::
1624 ERROR OUTPUT::
1626
1625
1627 id : <id_given_in_input>
1626 id : <id_given_in_input>
1628 result : null
1627 result : null
1629 error : {
1628 error : {
1630 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1629 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1631 }
1630 }
1632
1631
1633 """
1632 """
1634 repo = get_repo_or_error(repoid)
1633 repo = get_repo_or_error(repoid)
1635 perm = get_perm_or_error(perm)
1634 perm = get_perm_or_error(perm)
1636 user_group = get_user_group_or_error(usergroupid)
1635 user_group = get_user_group_or_error(usergroupid)
1637 if not HasPermissionAny('hg.admin')():
1636 if not HasPermissionAny('hg.admin')():
1638 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1637 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1639 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1638 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1640
1639
1641 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1640 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1642 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1641 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1643
1642
1644 try:
1643 try:
1645 RepoModel().grant_user_group_permission(
1644 RepoModel().grant_user_group_permission(
1646 repo=repo, group_name=user_group, perm=perm)
1645 repo=repo, group_name=user_group, perm=perm)
1647
1646
1648 meta.Session().commit()
1647 meta.Session().commit()
1649 return dict(
1648 return dict(
1650 msg='Granted perm: `%s` for user group: `%s` in '
1649 msg='Granted perm: `%s` for user group: `%s` in '
1651 'repo: `%s`' % (
1650 'repo: `%s`' % (
1652 perm.permission_name, user_group.users_group_name,
1651 perm.permission_name, user_group.users_group_name,
1653 repo.repo_name
1652 repo.repo_name
1654 ),
1653 ),
1655 success=True
1654 success=True
1656 )
1655 )
1657 except Exception:
1656 except Exception:
1658 log.error(traceback.format_exc())
1657 log.error(traceback.format_exc())
1659 raise JSONRPCError(
1658 raise JSONRPCError(
1660 'failed to edit permission for user group: `%s` in '
1659 'failed to edit permission for user group: `%s` in '
1661 'repo: `%s`' % (
1660 'repo: `%s`' % (
1662 usergroupid, repo.repo_name
1661 usergroupid, repo.repo_name
1663 )
1662 )
1664 )
1663 )
1665
1664
1666 # permission check inside
1665 # permission check inside
1667 def revoke_user_group_permission(self, repoid, usergroupid):
1666 def revoke_user_group_permission(self, repoid, usergroupid):
1668 """
1667 """
1669 Revoke permission for user group on given repository. This command can be
1668 Revoke permission for user group on given repository. This command can be
1670 executed only using api_key belonging to user with admin rights.
1669 executed only using api_key belonging to user with admin rights.
1671
1670
1672 :param repoid: repository name or repository id
1671 :param repoid: repository name or repository id
1673 :type repoid: str or int
1672 :type repoid: str or int
1674 :param usergroupid:
1673 :param usergroupid:
1675
1674
1676 OUTPUT::
1675 OUTPUT::
1677
1676
1678 id : <id_given_in_input>
1677 id : <id_given_in_input>
1679 result: {
1678 result: {
1680 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1679 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1681 "success": true
1680 "success": true
1682 }
1681 }
1683 error: null
1682 error: null
1684 """
1683 """
1685 repo = get_repo_or_error(repoid)
1684 repo = get_repo_or_error(repoid)
1686 user_group = get_user_group_or_error(usergroupid)
1685 user_group = get_user_group_or_error(usergroupid)
1687 if not HasPermissionAny('hg.admin')():
1686 if not HasPermissionAny('hg.admin')():
1688 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1687 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1689 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1688 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1690
1689
1691 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1690 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1692 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1691 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1693
1692
1694 try:
1693 try:
1695 RepoModel().revoke_user_group_permission(
1694 RepoModel().revoke_user_group_permission(
1696 repo=repo, group_name=user_group)
1695 repo=repo, group_name=user_group)
1697
1696
1698 meta.Session().commit()
1697 meta.Session().commit()
1699 return dict(
1698 return dict(
1700 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
1699 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
1701 user_group.users_group_name, repo.repo_name
1700 user_group.users_group_name, repo.repo_name
1702 ),
1701 ),
1703 success=True
1702 success=True
1704 )
1703 )
1705 except Exception:
1704 except Exception:
1706 log.error(traceback.format_exc())
1705 log.error(traceback.format_exc())
1707 raise JSONRPCError(
1706 raise JSONRPCError(
1708 'failed to edit permission for user group: `%s` in '
1707 'failed to edit permission for user group: `%s` in '
1709 'repo: `%s`' % (
1708 'repo: `%s`' % (
1710 user_group.users_group_name, repo.repo_name
1709 user_group.users_group_name, repo.repo_name
1711 )
1710 )
1712 )
1711 )
1713
1712
1714 @HasPermissionAnyDecorator('hg.admin')
1713 @HasPermissionAnyDecorator('hg.admin')
1715 def get_repo_group(self, repogroupid):
1714 def get_repo_group(self, repogroupid):
1716 """
1715 """
1717 Returns given repo group together with permissions, and repositories
1716 Returns given repo group together with permissions, and repositories
1718 inside the group
1717 inside the group
1719
1718
1720 :param repogroupid: id/name of repository group
1719 :param repogroupid: id/name of repository group
1721 :type repogroupid: str or int
1720 :type repogroupid: str or int
1722 """
1721 """
1723 repo_group = get_repo_group_or_error(repogroupid)
1722 repo_group = get_repo_group_or_error(repogroupid)
1724
1723
1725 members = []
1724 members = []
1726 for user in repo_group.repo_group_to_perm:
1725 for user in repo_group.repo_group_to_perm:
1727 perm = user.permission.permission_name
1726 perm = user.permission.permission_name
1728 user = user.user
1727 user = user.user
1729 user_data = {
1728 user_data = {
1730 'name': user.username,
1729 'name': user.username,
1731 'type': "user",
1730 'type': "user",
1732 'permission': perm
1731 'permission': perm
1733 }
1732 }
1734 members.append(user_data)
1733 members.append(user_data)
1735
1734
1736 for user_group in repo_group.users_group_to_perm:
1735 for user_group in repo_group.users_group_to_perm:
1737 perm = user_group.permission.permission_name
1736 perm = user_group.permission.permission_name
1738 user_group = user_group.users_group
1737 user_group = user_group.users_group
1739 user_group_data = {
1738 user_group_data = {
1740 'name': user_group.users_group_name,
1739 'name': user_group.users_group_name,
1741 'type': "user_group",
1740 'type': "user_group",
1742 'permission': perm
1741 'permission': perm
1743 }
1742 }
1744 members.append(user_group_data)
1743 members.append(user_group_data)
1745
1744
1746 data = repo_group.get_api_data()
1745 data = repo_group.get_api_data()
1747 data["members"] = members
1746 data["members"] = members
1748 return data
1747 return data
1749
1748
1750 @HasPermissionAnyDecorator('hg.admin')
1749 @HasPermissionAnyDecorator('hg.admin')
1751 def get_repo_groups(self):
1750 def get_repo_groups(self):
1752 """
1751 """
1753 Returns all repository groups
1752 Returns all repository groups
1754
1753
1755 """
1754 """
1756 return [
1755 return [
1757 repo_group.get_api_data()
1756 repo_group.get_api_data()
1758 for repo_group in db.RepoGroup.query()
1757 for repo_group in db.RepoGroup.query()
1759 ]
1758 ]
1760
1759
1761 @HasPermissionAnyDecorator('hg.admin')
1760 @HasPermissionAnyDecorator('hg.admin')
1762 def create_repo_group(self, group_name, description='',
1761 def create_repo_group(self, group_name, description='',
1763 owner=None,
1762 owner=None,
1764 parent=None,
1763 parent=None,
1765 copy_permissions=False):
1764 copy_permissions=False):
1766 """
1765 """
1767 Creates a repository group. This command can be executed only using
1766 Creates a repository group. This command can be executed only using
1768 api_key belonging to user with admin rights.
1767 api_key belonging to user with admin rights.
1769
1768
1770 :param group_name:
1769 :param group_name:
1771 :type group_name:
1770 :type group_name:
1772 :param description:
1771 :param description:
1773 :type description:
1772 :type description:
1774 :param owner:
1773 :param owner:
1775 :type owner:
1774 :type owner:
1776 :param parent:
1775 :param parent:
1777 :type parent:
1776 :type parent:
1778 :param copy_permissions:
1777 :param copy_permissions:
1779 :type copy_permissions:
1778 :type copy_permissions:
1780
1779
1781 OUTPUT::
1780 OUTPUT::
1782
1781
1783 id : <id_given_in_input>
1782 id : <id_given_in_input>
1784 result : {
1783 result : {
1785 "msg": "created new repo group `<repo_group_name>`"
1784 "msg": "created new repo group `<repo_group_name>`"
1786 "repo_group": <repogroup_object>
1785 "repo_group": <repogroup_object>
1787 }
1786 }
1788 error : null
1787 error : null
1789
1788
1790 ERROR OUTPUT::
1789 ERROR OUTPUT::
1791
1790
1792 id : <id_given_in_input>
1791 id : <id_given_in_input>
1793 result : null
1792 result : null
1794 error : {
1793 error : {
1795 failed to create repo group `<repogroupid>`
1794 failed to create repo group `<repogroupid>`
1796 }
1795 }
1797
1796
1798 """
1797 """
1799 if db.RepoGroup.get_by_group_name(group_name):
1798 if db.RepoGroup.get_by_group_name(group_name):
1800 raise JSONRPCError("repo group `%s` already exist" % (group_name,))
1799 raise JSONRPCError("repo group `%s` already exist" % (group_name,))
1801
1800
1802 if owner is None:
1801 if owner is None:
1803 owner = request.authuser.user_id
1802 owner = request.authuser.user_id
1804 group_description = description
1803 group_description = description
1805 parent_group = None
1804 parent_group = None
1806 if parent is not None:
1805 if parent is not None:
1807 parent_group = get_repo_group_or_error(parent)
1806 parent_group = get_repo_group_or_error(parent)
1808
1807
1809 try:
1808 try:
1810 repo_group = RepoGroupModel().create(
1809 repo_group = RepoGroupModel().create(
1811 group_name=group_name,
1810 group_name=group_name,
1812 group_description=group_description,
1811 group_description=group_description,
1813 owner=owner,
1812 owner=owner,
1814 parent=parent_group,
1813 parent=parent_group,
1815 copy_permissions=copy_permissions
1814 copy_permissions=copy_permissions
1816 )
1815 )
1817 meta.Session().commit()
1816 meta.Session().commit()
1818 return dict(
1817 return dict(
1819 msg='created new repo group `%s`' % group_name,
1818 msg='created new repo group `%s`' % group_name,
1820 repo_group=repo_group.get_api_data()
1819 repo_group=repo_group.get_api_data()
1821 )
1820 )
1822 except Exception:
1821 except Exception:
1823
1822
1824 log.error(traceback.format_exc())
1823 log.error(traceback.format_exc())
1825 raise JSONRPCError('failed to create repo group `%s`' % (group_name,))
1824 raise JSONRPCError('failed to create repo group `%s`' % (group_name,))
1826
1825
1827 @HasPermissionAnyDecorator('hg.admin')
1826 @HasPermissionAnyDecorator('hg.admin')
1828 def update_repo_group(self, repogroupid, group_name=None,
1827 def update_repo_group(self, repogroupid, group_name=None,
1829 description=None,
1828 description=None,
1830 owner=None,
1829 owner=None,
1831 parent=None):
1830 parent=None):
1832 repo_group = get_repo_group_or_error(repogroupid)
1831 repo_group = get_repo_group_or_error(repogroupid)
1833
1832
1834 updates = {}
1833 updates = {}
1835 try:
1834 try:
1836 store_update(updates, group_name, 'group_name')
1835 store_update(updates, group_name, 'group_name')
1837 store_update(updates, description, 'group_description')
1836 store_update(updates, description, 'group_description')
1838 store_update(updates, owner, 'owner')
1837 store_update(updates, owner, 'owner')
1839 store_update(updates, parent, 'parent_group')
1838 store_update(updates, parent, 'parent_group')
1840 repo_group = RepoGroupModel().update(repo_group, updates)
1839 repo_group = RepoGroupModel().update(repo_group, updates)
1841 meta.Session().commit()
1840 meta.Session().commit()
1842 return dict(
1841 return dict(
1843 msg='updated repository group ID:%s %s' % (repo_group.group_id,
1842 msg='updated repository group ID:%s %s' % (repo_group.group_id,
1844 repo_group.group_name),
1843 repo_group.group_name),
1845 repo_group=repo_group.get_api_data()
1844 repo_group=repo_group.get_api_data()
1846 )
1845 )
1847 except Exception:
1846 except Exception:
1848 log.error(traceback.format_exc())
1847 log.error(traceback.format_exc())
1849 raise JSONRPCError('failed to update repository group `%s`'
1848 raise JSONRPCError('failed to update repository group `%s`'
1850 % (repogroupid,))
1849 % (repogroupid,))
1851
1850
1852 @HasPermissionAnyDecorator('hg.admin')
1851 @HasPermissionAnyDecorator('hg.admin')
1853 def delete_repo_group(self, repogroupid):
1852 def delete_repo_group(self, repogroupid):
1854 """
1853 """
1855
1854
1856 :param repogroupid: name or id of repository group
1855 :param repogroupid: name or id of repository group
1857 :type repogroupid: str or int
1856 :type repogroupid: str or int
1858
1857
1859 OUTPUT::
1858 OUTPUT::
1860
1859
1861 id : <id_given_in_input>
1860 id : <id_given_in_input>
1862 result : {
1861 result : {
1863 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
1862 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
1864 'repo_group': null
1863 'repo_group': null
1865 }
1864 }
1866 error : null
1865 error : null
1867
1866
1868 ERROR OUTPUT::
1867 ERROR OUTPUT::
1869
1868
1870 id : <id_given_in_input>
1869 id : <id_given_in_input>
1871 result : null
1870 result : null
1872 error : {
1871 error : {
1873 "failed to delete repo group ID:<repogroupid> <repogroupname>"
1872 "failed to delete repo group ID:<repogroupid> <repogroupname>"
1874 }
1873 }
1875
1874
1876 """
1875 """
1877 repo_group = get_repo_group_or_error(repogroupid)
1876 repo_group = get_repo_group_or_error(repogroupid)
1878
1877
1879 try:
1878 try:
1880 RepoGroupModel().delete(repo_group)
1879 RepoGroupModel().delete(repo_group)
1881 meta.Session().commit()
1880 meta.Session().commit()
1882 return dict(
1881 return dict(
1883 msg='deleted repo group ID:%s %s' %
1882 msg='deleted repo group ID:%s %s' %
1884 (repo_group.group_id, repo_group.group_name),
1883 (repo_group.group_id, repo_group.group_name),
1885 repo_group=None
1884 repo_group=None
1886 )
1885 )
1887 except Exception:
1886 except Exception:
1888 log.error(traceback.format_exc())
1887 log.error(traceback.format_exc())
1889 raise JSONRPCError('failed to delete repo group ID:%s %s' %
1888 raise JSONRPCError('failed to delete repo group ID:%s %s' %
1890 (repo_group.group_id, repo_group.group_name)
1889 (repo_group.group_id, repo_group.group_name)
1891 )
1890 )
1892
1891
1893 # permission check inside
1892 # permission check inside
1894 def grant_user_permission_to_repo_group(self, repogroupid, userid,
1893 def grant_user_permission_to_repo_group(self, repogroupid, userid,
1895 perm, apply_to_children='none'):
1894 perm, apply_to_children='none'):
1896 """
1895 """
1897 Grant permission for user on given repository group, or update existing
1896 Grant permission for user on given repository group, or update existing
1898 one if found. This command can be executed only using api_key belonging
1897 one if found. This command can be executed only using api_key belonging
1899 to user with admin rights, or user who has admin right to given repository
1898 to user with admin rights, or user who has admin right to given repository
1900 group.
1899 group.
1901
1900
1902 :param repogroupid: name or id of repository group
1901 :param repogroupid: name or id of repository group
1903 :type repogroupid: str or int
1902 :type repogroupid: str or int
1904 :param userid:
1903 :param userid:
1905 :param perm: (group.(none|read|write|admin))
1904 :param perm: (group.(none|read|write|admin))
1906 :type perm: str
1905 :type perm: str
1907 :param apply_to_children: 'none', 'repos', 'groups', 'all'
1906 :param apply_to_children: 'none', 'repos', 'groups', 'all'
1908 :type apply_to_children: str
1907 :type apply_to_children: str
1909
1908
1910 OUTPUT::
1909 OUTPUT::
1911
1910
1912 id : <id_given_in_input>
1911 id : <id_given_in_input>
1913 result: {
1912 result: {
1914 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
1913 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
1915 "success": true
1914 "success": true
1916 }
1915 }
1917 error: null
1916 error: null
1918
1917
1919 ERROR OUTPUT::
1918 ERROR OUTPUT::
1920
1919
1921 id : <id_given_in_input>
1920 id : <id_given_in_input>
1922 result : null
1921 result : null
1923 error : {
1922 error : {
1924 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
1923 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
1925 }
1924 }
1926
1925
1927 """
1926 """
1928
1927
1929 repo_group = get_repo_group_or_error(repogroupid)
1928 repo_group = get_repo_group_or_error(repogroupid)
1930
1929
1931 if not HasPermissionAny('hg.admin')():
1930 if not HasPermissionAny('hg.admin')():
1932 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
1931 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
1933 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
1932 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
1934
1933
1935 user = get_user_or_error(userid)
1934 user = get_user_or_error(userid)
1936 perm = get_perm_or_error(perm, prefix='group.')
1935 perm = get_perm_or_error(perm, prefix='group.')
1937
1936
1938 try:
1937 try:
1939 RepoGroupModel().add_permission(repo_group=repo_group,
1938 RepoGroupModel().add_permission(repo_group=repo_group,
1940 obj=user,
1939 obj=user,
1941 obj_type="user",
1940 obj_type="user",
1942 perm=perm,
1941 perm=perm,
1943 recursive=apply_to_children)
1942 recursive=apply_to_children)
1944 meta.Session().commit()
1943 meta.Session().commit()
1945 return dict(
1944 return dict(
1946 msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1945 msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1947 perm.permission_name, apply_to_children, user.username, repo_group.name
1946 perm.permission_name, apply_to_children, user.username, repo_group.name
1948 ),
1947 ),
1949 success=True
1948 success=True
1950 )
1949 )
1951 except Exception:
1950 except Exception:
1952 log.error(traceback.format_exc())
1951 log.error(traceback.format_exc())
1953 raise JSONRPCError(
1952 raise JSONRPCError(
1954 'failed to edit permission for user: `%s` in repo group: `%s`' % (
1953 'failed to edit permission for user: `%s` in repo group: `%s`' % (
1955 userid, repo_group.name))
1954 userid, repo_group.name))
1956
1955
1957 # permission check inside
1956 # permission check inside
1958 def revoke_user_permission_from_repo_group(self, repogroupid, userid,
1957 def revoke_user_permission_from_repo_group(self, repogroupid, userid,
1959 apply_to_children='none'):
1958 apply_to_children='none'):
1960 """
1959 """
1961 Revoke permission for user on given repository group. This command can
1960 Revoke permission for user on given repository group. This command can
1962 be executed only using api_key belonging to user with admin rights, or
1961 be executed only using api_key belonging to user with admin rights, or
1963 user who has admin right to given repository group.
1962 user who has admin right to given repository group.
1964
1963
1965 :param repogroupid: name or id of repository group
1964 :param repogroupid: name or id of repository group
1966 :type repogroupid: str or int
1965 :type repogroupid: str or int
1967 :param userid:
1966 :param userid:
1968 :type userid:
1967 :type userid:
1969 :param apply_to_children: 'none', 'repos', 'groups', 'all'
1968 :param apply_to_children: 'none', 'repos', 'groups', 'all'
1970 :type apply_to_children: str
1969 :type apply_to_children: str
1971
1970
1972 OUTPUT::
1971 OUTPUT::
1973
1972
1974 id : <id_given_in_input>
1973 id : <id_given_in_input>
1975 result: {
1974 result: {
1976 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
1975 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
1977 "success": true
1976 "success": true
1978 }
1977 }
1979 error: null
1978 error: null
1980
1979
1981 ERROR OUTPUT::
1980 ERROR OUTPUT::
1982
1981
1983 id : <id_given_in_input>
1982 id : <id_given_in_input>
1984 result : null
1983 result : null
1985 error : {
1984 error : {
1986 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
1985 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
1987 }
1986 }
1988
1987
1989 """
1988 """
1990
1989
1991 repo_group = get_repo_group_or_error(repogroupid)
1990 repo_group = get_repo_group_or_error(repogroupid)
1992
1991
1993 if not HasPermissionAny('hg.admin')():
1992 if not HasPermissionAny('hg.admin')():
1994 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
1993 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
1995 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
1994 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
1996
1995
1997 user = get_user_or_error(userid)
1996 user = get_user_or_error(userid)
1998
1997
1999 try:
1998 try:
2000 RepoGroupModel().delete_permission(repo_group=repo_group,
1999 RepoGroupModel().delete_permission(repo_group=repo_group,
2001 obj=user,
2000 obj=user,
2002 obj_type="user",
2001 obj_type="user",
2003 recursive=apply_to_children)
2002 recursive=apply_to_children)
2004
2003
2005 meta.Session().commit()
2004 meta.Session().commit()
2006 return dict(
2005 return dict(
2007 msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2006 msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2008 apply_to_children, user.username, repo_group.name
2007 apply_to_children, user.username, repo_group.name
2009 ),
2008 ),
2010 success=True
2009 success=True
2011 )
2010 )
2012 except Exception:
2011 except Exception:
2013 log.error(traceback.format_exc())
2012 log.error(traceback.format_exc())
2014 raise JSONRPCError(
2013 raise JSONRPCError(
2015 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2014 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2016 userid, repo_group.name))
2015 userid, repo_group.name))
2017
2016
2018 # permission check inside
2017 # permission check inside
2019 def grant_user_group_permission_to_repo_group(
2018 def grant_user_group_permission_to_repo_group(
2020 self, repogroupid, usergroupid, perm,
2019 self, repogroupid, usergroupid, perm,
2021 apply_to_children='none'):
2020 apply_to_children='none'):
2022 """
2021 """
2023 Grant permission for user group on given repository group, or update
2022 Grant permission for user group on given repository group, or update
2024 existing one if found. This command can be executed only using
2023 existing one if found. This command can be executed only using
2025 api_key belonging to user with admin rights, or user who has admin
2024 api_key belonging to user with admin rights, or user who has admin
2026 right to given repository group.
2025 right to given repository group.
2027
2026
2028 :param repogroupid: name or id of repository group
2027 :param repogroupid: name or id of repository group
2029 :type repogroupid: str or int
2028 :type repogroupid: str or int
2030 :param usergroupid: id of usergroup
2029 :param usergroupid: id of usergroup
2031 :type usergroupid: str or int
2030 :type usergroupid: str or int
2032 :param perm: (group.(none|read|write|admin))
2031 :param perm: (group.(none|read|write|admin))
2033 :type perm: str
2032 :type perm: str
2034 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2033 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2035 :type apply_to_children: str
2034 :type apply_to_children: str
2036
2035
2037 OUTPUT::
2036 OUTPUT::
2038
2037
2039 id : <id_given_in_input>
2038 id : <id_given_in_input>
2040 result : {
2039 result : {
2041 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2040 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2042 "success": true
2041 "success": true
2043
2042
2044 }
2043 }
2045 error : null
2044 error : null
2046
2045
2047 ERROR OUTPUT::
2046 ERROR OUTPUT::
2048
2047
2049 id : <id_given_in_input>
2048 id : <id_given_in_input>
2050 result : null
2049 result : null
2051 error : {
2050 error : {
2052 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2051 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2053 }
2052 }
2054
2053
2055 """
2054 """
2056 repo_group = get_repo_group_or_error(repogroupid)
2055 repo_group = get_repo_group_or_error(repogroupid)
2057 perm = get_perm_or_error(perm, prefix='group.')
2056 perm = get_perm_or_error(perm, prefix='group.')
2058 user_group = get_user_group_or_error(usergroupid)
2057 user_group = get_user_group_or_error(usergroupid)
2059 if not HasPermissionAny('hg.admin')():
2058 if not HasPermissionAny('hg.admin')():
2060 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2059 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2061 raise JSONRPCError(
2060 raise JSONRPCError(
2062 'repository group `%s` does not exist' % (repogroupid,))
2061 'repository group `%s` does not exist' % (repogroupid,))
2063
2062
2064 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2063 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2065 raise JSONRPCError(
2064 raise JSONRPCError(
2066 'user group `%s` does not exist' % (usergroupid,))
2065 'user group `%s` does not exist' % (usergroupid,))
2067
2066
2068 try:
2067 try:
2069 RepoGroupModel().add_permission(repo_group=repo_group,
2068 RepoGroupModel().add_permission(repo_group=repo_group,
2070 obj=user_group,
2069 obj=user_group,
2071 obj_type="user_group",
2070 obj_type="user_group",
2072 perm=perm,
2071 perm=perm,
2073 recursive=apply_to_children)
2072 recursive=apply_to_children)
2074 meta.Session().commit()
2073 meta.Session().commit()
2075 return dict(
2074 return dict(
2076 msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2075 msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2077 perm.permission_name, apply_to_children,
2076 perm.permission_name, apply_to_children,
2078 user_group.users_group_name, repo_group.name
2077 user_group.users_group_name, repo_group.name
2079 ),
2078 ),
2080 success=True
2079 success=True
2081 )
2080 )
2082 except Exception:
2081 except Exception:
2083 log.error(traceback.format_exc())
2082 log.error(traceback.format_exc())
2084 raise JSONRPCError(
2083 raise JSONRPCError(
2085 'failed to edit permission for user group: `%s` in '
2084 'failed to edit permission for user group: `%s` in '
2086 'repo group: `%s`' % (
2085 'repo group: `%s`' % (
2087 usergroupid, repo_group.name
2086 usergroupid, repo_group.name
2088 )
2087 )
2089 )
2088 )
2090
2089
2091 # permission check inside
2090 # permission check inside
2092 def revoke_user_group_permission_from_repo_group(
2091 def revoke_user_group_permission_from_repo_group(
2093 self, repogroupid, usergroupid,
2092 self, repogroupid, usergroupid,
2094 apply_to_children='none'):
2093 apply_to_children='none'):
2095 """
2094 """
2096 Revoke permission for user group on given repository. This command can be
2095 Revoke permission for user group on given repository. This command can be
2097 executed only using api_key belonging to user with admin rights, or
2096 executed only using api_key belonging to user with admin rights, or
2098 user who has admin right to given repository group.
2097 user who has admin right to given repository group.
2099
2098
2100 :param repogroupid: name or id of repository group
2099 :param repogroupid: name or id of repository group
2101 :type repogroupid: str or int
2100 :type repogroupid: str or int
2102 :param usergroupid:
2101 :param usergroupid:
2103 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2102 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2104 :type apply_to_children: str
2103 :type apply_to_children: str
2105
2104
2106 OUTPUT::
2105 OUTPUT::
2107
2106
2108 id : <id_given_in_input>
2107 id : <id_given_in_input>
2109 result: {
2108 result: {
2110 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2109 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2111 "success": true
2110 "success": true
2112 }
2111 }
2113 error: null
2112 error: null
2114
2113
2115 ERROR OUTPUT::
2114 ERROR OUTPUT::
2116
2115
2117 id : <id_given_in_input>
2116 id : <id_given_in_input>
2118 result : null
2117 result : null
2119 error : {
2118 error : {
2120 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2119 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2121 }
2120 }
2122
2121
2123
2122
2124 """
2123 """
2125 repo_group = get_repo_group_or_error(repogroupid)
2124 repo_group = get_repo_group_or_error(repogroupid)
2126 user_group = get_user_group_or_error(usergroupid)
2125 user_group = get_user_group_or_error(usergroupid)
2127 if not HasPermissionAny('hg.admin')():
2126 if not HasPermissionAny('hg.admin')():
2128 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2127 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2129 raise JSONRPCError(
2128 raise JSONRPCError(
2130 'repository group `%s` does not exist' % (repogroupid,))
2129 'repository group `%s` does not exist' % (repogroupid,))
2131
2130
2132 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2131 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2133 raise JSONRPCError(
2132 raise JSONRPCError(
2134 'user group `%s` does not exist' % (usergroupid,))
2133 'user group `%s` does not exist' % (usergroupid,))
2135
2134
2136 try:
2135 try:
2137 RepoGroupModel().delete_permission(repo_group=repo_group,
2136 RepoGroupModel().delete_permission(repo_group=repo_group,
2138 obj=user_group,
2137 obj=user_group,
2139 obj_type="user_group",
2138 obj_type="user_group",
2140 recursive=apply_to_children)
2139 recursive=apply_to_children)
2141 meta.Session().commit()
2140 meta.Session().commit()
2142 return dict(
2141 return dict(
2143 msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2142 msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2144 apply_to_children, user_group.users_group_name, repo_group.name
2143 apply_to_children, user_group.users_group_name, repo_group.name
2145 ),
2144 ),
2146 success=True
2145 success=True
2147 )
2146 )
2148 except Exception:
2147 except Exception:
2149 log.error(traceback.format_exc())
2148 log.error(traceback.format_exc())
2150 raise JSONRPCError(
2149 raise JSONRPCError(
2151 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2150 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2152 user_group.users_group_name, repo_group.name
2151 user_group.users_group_name, repo_group.name
2153 )
2152 )
2154 )
2153 )
2155
2154
2156 def get_gist(self, gistid):
2155 def get_gist(self, gistid):
2157 """
2156 """
2158 Get given gist by id
2157 Get given gist by id
2159
2158
2160 :param gistid: id of private or public gist
2159 :param gistid: id of private or public gist
2161 :type gistid: str
2160 :type gistid: str
2162 """
2161 """
2163 gist = get_gist_or_error(gistid)
2162 gist = get_gist_or_error(gistid)
2164 if not HasPermissionAny('hg.admin')():
2163 if not HasPermissionAny('hg.admin')():
2165 if gist.owner_id != request.authuser.user_id:
2164 if gist.owner_id != request.authuser.user_id:
2166 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2165 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2167 return gist.get_api_data()
2166 return gist.get_api_data()
2168
2167
2169 def get_gists(self, userid=None):
2168 def get_gists(self, userid=None):
2170 """
2169 """
2171 Get all gists for given user. If userid is empty returned gists
2170 Get all gists for given user. If userid is empty returned gists
2172 are for user who called the api
2171 are for user who called the api
2173
2172
2174 :param userid: user to get gists for
2173 :param userid: user to get gists for
2175 :type userid: Optional(str or int)
2174 :type userid: Optional(str or int)
2176 """
2175 """
2177 if not HasPermissionAny('hg.admin')():
2176 if not HasPermissionAny('hg.admin')():
2178 # make sure normal user does not pass someone else userid,
2177 # make sure normal user does not pass someone else userid,
2179 # he is not allowed to do that
2178 # he is not allowed to do that
2180 if userid is not None and userid != request.authuser.user_id:
2179 if userid is not None and userid != request.authuser.user_id:
2181 raise JSONRPCError(
2180 raise JSONRPCError(
2182 'userid is not the same as your user'
2181 'userid is not the same as your user'
2183 )
2182 )
2184
2183
2185 if userid is None:
2184 if userid is None:
2186 user_id = request.authuser.user_id
2185 user_id = request.authuser.user_id
2187 else:
2186 else:
2188 user_id = get_user_or_error(userid).user_id
2187 user_id = get_user_or_error(userid).user_id
2189
2188
2190 return [
2189 return [
2191 gist.get_api_data()
2190 gist.get_api_data()
2192 for gist in db.Gist().query()
2191 for gist in db.Gist().query()
2193 .filter_by(is_expired=False)
2192 .filter_by(is_expired=False)
2194 .filter(db.Gist.owner_id == user_id)
2193 .filter(db.Gist.owner_id == user_id)
2195 .order_by(db.Gist.created_on.desc())
2194 .order_by(db.Gist.created_on.desc())
2196 ]
2195 ]
2197
2196
2198 def create_gist(self, files, owner=None,
2197 def create_gist(self, files, owner=None,
2199 gist_type=db.Gist.GIST_PUBLIC, lifetime=-1,
2198 gist_type=db.Gist.GIST_PUBLIC, lifetime=-1,
2200 description=''):
2199 description=''):
2201
2200
2202 """
2201 """
2203 Creates new Gist
2202 Creates new Gist
2204
2203
2205 :param files: files to be added to gist
2204 :param files: files to be added to gist
2206 {'filename': {'content':'...', 'lexer': null},
2205 {'filename': {'content':'...', 'lexer': null},
2207 'filename2': {'content':'...', 'lexer': null}}
2206 'filename2': {'content':'...', 'lexer': null}}
2208 :type files: dict
2207 :type files: dict
2209 :param owner: gist owner, defaults to api method caller
2208 :param owner: gist owner, defaults to api method caller
2210 :type owner: Optional(str or int)
2209 :type owner: Optional(str or int)
2211 :param gist_type: type of gist 'public' or 'private'
2210 :param gist_type: type of gist 'public' or 'private'
2212 :type gist_type: Optional(str)
2211 :type gist_type: Optional(str)
2213 :param lifetime: time in minutes of gist lifetime
2212 :param lifetime: time in minutes of gist lifetime
2214 :type lifetime: Optional(int)
2213 :type lifetime: Optional(int)
2215 :param description: gist description
2214 :param description: gist description
2216 :type description: Optional(str)
2215 :type description: Optional(str)
2217
2216
2218 OUTPUT::
2217 OUTPUT::
2219
2218
2220 id : <id_given_in_input>
2219 id : <id_given_in_input>
2221 result : {
2220 result : {
2222 "msg": "created new gist",
2221 "msg": "created new gist",
2223 "gist": {}
2222 "gist": {}
2224 }
2223 }
2225 error : null
2224 error : null
2226
2225
2227 ERROR OUTPUT::
2226 ERROR OUTPUT::
2228
2227
2229 id : <id_given_in_input>
2228 id : <id_given_in_input>
2230 result : null
2229 result : null
2231 error : {
2230 error : {
2232 "failed to create gist"
2231 "failed to create gist"
2233 }
2232 }
2234
2233
2235 """
2234 """
2236 try:
2235 try:
2237 if owner is None:
2236 if owner is None:
2238 owner = request.authuser.user_id
2237 owner = request.authuser.user_id
2239
2238
2240 owner = get_user_or_error(owner)
2239 owner = get_user_or_error(owner)
2241
2240
2242 gist = GistModel().create(description=description,
2241 gist = GistModel().create(description=description,
2243 owner=owner,
2242 owner=owner,
2244 ip_addr=request.ip_addr,
2243 ip_addr=request.ip_addr,
2245 gist_mapping=files,
2244 gist_mapping=files,
2246 gist_type=gist_type,
2245 gist_type=gist_type,
2247 lifetime=lifetime)
2246 lifetime=lifetime)
2248 meta.Session().commit()
2247 meta.Session().commit()
2249 return dict(
2248 return dict(
2250 msg='created new gist',
2249 msg='created new gist',
2251 gist=gist.get_api_data()
2250 gist=gist.get_api_data()
2252 )
2251 )
2253 except Exception:
2252 except Exception:
2254 log.error(traceback.format_exc())
2253 log.error(traceback.format_exc())
2255 raise JSONRPCError('failed to create gist')
2254 raise JSONRPCError('failed to create gist')
2256
2255
2257 # permission check inside
2256 # permission check inside
2258 def delete_gist(self, gistid):
2257 def delete_gist(self, gistid):
2259 """
2258 """
2260 Deletes existing gist
2259 Deletes existing gist
2261
2260
2262 :param gistid: id of gist to delete
2261 :param gistid: id of gist to delete
2263 :type gistid: str
2262 :type gistid: str
2264
2263
2265 OUTPUT::
2264 OUTPUT::
2266
2265
2267 id : <id_given_in_input>
2266 id : <id_given_in_input>
2268 result : {
2267 result : {
2269 "deleted gist ID: <gist_id>",
2268 "deleted gist ID: <gist_id>",
2270 "gist": null
2269 "gist": null
2271 }
2270 }
2272 error : null
2271 error : null
2273
2272
2274 ERROR OUTPUT::
2273 ERROR OUTPUT::
2275
2274
2276 id : <id_given_in_input>
2275 id : <id_given_in_input>
2277 result : null
2276 result : null
2278 error : {
2277 error : {
2279 "failed to delete gist ID:<gist_id>"
2278 "failed to delete gist ID:<gist_id>"
2280 }
2279 }
2281
2280
2282 """
2281 """
2283 gist = get_gist_or_error(gistid)
2282 gist = get_gist_or_error(gistid)
2284 if not HasPermissionAny('hg.admin')():
2283 if not HasPermissionAny('hg.admin')():
2285 if gist.owner_id != request.authuser.user_id:
2284 if gist.owner_id != request.authuser.user_id:
2286 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2285 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2287
2286
2288 try:
2287 try:
2289 GistModel().delete(gist)
2288 GistModel().delete(gist)
2290 meta.Session().commit()
2289 meta.Session().commit()
2291 return dict(
2290 return dict(
2292 msg='deleted gist ID:%s' % (gist.gist_access_id,),
2291 msg='deleted gist ID:%s' % (gist.gist_access_id,),
2293 gist=None
2292 gist=None
2294 )
2293 )
2295 except Exception:
2294 except Exception:
2296 log.error(traceback.format_exc())
2295 log.error(traceback.format_exc())
2297 raise JSONRPCError('failed to delete gist ID:%s'
2296 raise JSONRPCError('failed to delete gist ID:%s'
2298 % (gist.gist_access_id,))
2297 % (gist.gist_access_id,))
2299
2298
2300 # permission check inside
2299 # permission check inside
2301 def get_changesets(self, repoid, start=None, end=None, start_date=None,
2300 def get_changesets(self, repoid, start=None, end=None, start_date=None,
2302 end_date=None, branch_name=None, reverse=False, with_file_list=False, max_revisions=None):
2301 end_date=None, branch_name=None, reverse=False, with_file_list=False, max_revisions=None):
2303 repo = get_repo_or_error(repoid)
2302 repo = get_repo_or_error(repoid)
2304 if not HasRepoPermissionLevel('read')(repo.repo_name):
2303 if not HasRepoPermissionLevel('read')(repo.repo_name):
2305 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2304 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2306
2305
2307 format = "%Y-%m-%dT%H:%M:%S"
2306 format = "%Y-%m-%dT%H:%M:%S"
2308 try:
2307 try:
2309 return [e.__json__(with_file_list) for e in
2308 return [e.__json__(with_file_list) for e in
2310 repo.scm_instance.get_changesets(start,
2309 repo.scm_instance.get_changesets(start,
2311 end,
2310 end,
2312 datetime.strptime(start_date, format) if start_date else None,
2311 datetime.strptime(start_date, format) if start_date else None,
2313 datetime.strptime(end_date, format) if end_date else None,
2312 datetime.strptime(end_date, format) if end_date else None,
2314 branch_name,
2313 branch_name,
2315 reverse, max_revisions)]
2314 reverse, max_revisions)]
2316 except EmptyRepositoryError as e:
2315 except EmptyRepositoryError as e:
2317 raise JSONRPCError('Repository is empty')
2316 raise JSONRPCError('Repository is empty')
2318
2317
2319 # permission check inside
2318 # permission check inside
2320 def get_changeset(self, repoid, raw_id, with_reviews=False):
2319 def get_changeset(self, repoid, raw_id, with_reviews=False):
2321 repo = get_repo_or_error(repoid)
2320 repo = get_repo_or_error(repoid)
2322 if not HasRepoPermissionLevel('read')(repo.repo_name):
2321 if not HasRepoPermissionLevel('read')(repo.repo_name):
2323 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2322 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2324 changeset = repo.get_changeset(raw_id)
2323 changeset = repo.get_changeset(raw_id)
2325 if isinstance(changeset, EmptyChangeset):
2324 if isinstance(changeset, EmptyChangeset):
2326 raise JSONRPCError('Changeset %s does not exist' % raw_id)
2325 raise JSONRPCError('Changeset %s does not exist' % raw_id)
2327
2326
2328 info = dict(changeset.as_dict())
2327 info = dict(changeset.as_dict())
2329
2328
2330 if with_reviews:
2329 if with_reviews:
2331 reviews = ChangesetStatusModel().get_statuses(
2330 reviews = ChangesetStatusModel().get_statuses(
2332 repo.repo_name, raw_id)
2331 repo.repo_name, raw_id)
2333 info["reviews"] = reviews
2332 info["reviews"] = reviews
2334
2333
2335 return info
2334 return info
2336
2335
2337 # permission check inside
2336 # permission check inside
2338 def get_pullrequest(self, pullrequest_id):
2337 def get_pullrequest(self, pullrequest_id):
2339 """
2338 """
2340 Get given pull request by id
2339 Get given pull request by id
2341 """
2340 """
2342 pull_request = db.PullRequest.get(pullrequest_id)
2341 pull_request = db.PullRequest.get(pullrequest_id)
2343 if pull_request is None:
2342 if pull_request is None:
2344 raise JSONRPCError('pull request `%s` does not exist' % (pullrequest_id,))
2343 raise JSONRPCError('pull request `%s` does not exist' % (pullrequest_id,))
2345 if not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name):
2344 if not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name):
2346 raise JSONRPCError('not allowed')
2345 raise JSONRPCError('not allowed')
2347 return pull_request.get_api_data()
2346 return pull_request.get_api_data()
2348
2347
2349 # permission check inside
2348 # permission check inside
2350 def comment_pullrequest(self, pull_request_id, comment_msg='', status=None, close_pr=False):
2349 def comment_pullrequest(self, pull_request_id, comment_msg='', status=None, close_pr=False):
2351 """
2350 """
2352 Add comment, close and change status of pull request.
2351 Add comment, close and change status of pull request.
2353 """
2352 """
2354 apiuser = get_user_or_error(request.authuser.user_id)
2353 apiuser = get_user_or_error(request.authuser.user_id)
2355 pull_request = db.PullRequest.get(pull_request_id)
2354 pull_request = db.PullRequest.get(pull_request_id)
2356 if pull_request is None:
2355 if pull_request is None:
2357 raise JSONRPCError('pull request `%s` does not exist' % (pull_request_id,))
2356 raise JSONRPCError('pull request `%s` does not exist' % (pull_request_id,))
2358 if (not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name)):
2357 if (not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name)):
2359 raise JSONRPCError('No permission to add comment. User needs at least reading permissions'
2358 raise JSONRPCError('No permission to add comment. User needs at least reading permissions'
2360 ' to the source repository.')
2359 ' to the source repository.')
2361 owner = apiuser.user_id == pull_request.owner_id
2360 owner = apiuser.user_id == pull_request.owner_id
2362 reviewer = apiuser.user_id in [reviewer.user_id for reviewer in pull_request.reviewers]
2361 reviewer = apiuser.user_id in [reviewer.user_id for reviewer in pull_request.reviewers]
2363 if close_pr and not (apiuser.admin or owner):
2362 if close_pr and not (apiuser.admin or owner):
2364 raise JSONRPCError('No permission to close pull request. User needs to be admin or owner.')
2363 raise JSONRPCError('No permission to close pull request. User needs to be admin or owner.')
2365 if status and not (apiuser.admin or owner or reviewer):
2364 if status and not (apiuser.admin or owner or reviewer):
2366 raise JSONRPCError('No permission to change pull request status. User needs to be admin, owner or reviewer.')
2365 raise JSONRPCError('No permission to change pull request status. User needs to be admin, owner or reviewer.')
2367 if pull_request.is_closed():
2366 if pull_request.is_closed():
2368 raise JSONRPCError('pull request is already closed')
2367 raise JSONRPCError('pull request is already closed')
2369
2368
2370 comment = ChangesetCommentsModel().create(
2369 comment = ChangesetCommentsModel().create(
2371 text=comment_msg,
2370 text=comment_msg,
2372 repo=pull_request.org_repo.repo_id,
2371 repo=pull_request.org_repo.repo_id,
2373 author=apiuser.user_id,
2372 author=apiuser.user_id,
2374 pull_request=pull_request.pull_request_id,
2373 pull_request=pull_request.pull_request_id,
2375 f_path=None,
2374 f_path=None,
2376 line_no=None,
2375 line_no=None,
2377 status_change=db.ChangesetStatus.get_status_lbl(status),
2376 status_change=db.ChangesetStatus.get_status_lbl(status),
2378 closing_pr=close_pr
2377 closing_pr=close_pr
2379 )
2378 )
2380 userlog.action_logger(apiuser,
2379 userlog.action_logger(apiuser,
2381 'user_commented_pull_request:%s' % pull_request_id,
2380 'user_commented_pull_request:%s' % pull_request_id,
2382 pull_request.org_repo, request.ip_addr)
2381 pull_request.org_repo, request.ip_addr)
2383 if status:
2382 if status:
2384 ChangesetStatusModel().set_status(
2383 ChangesetStatusModel().set_status(
2385 pull_request.org_repo_id,
2384 pull_request.org_repo_id,
2386 status,
2385 status,
2387 apiuser.user_id,
2386 apiuser.user_id,
2388 comment,
2387 comment,
2389 pull_request=pull_request_id
2388 pull_request=pull_request_id
2390 )
2389 )
2391 if close_pr:
2390 if close_pr:
2392 PullRequestModel().close_pull_request(pull_request_id)
2391 PullRequestModel().close_pull_request(pull_request_id)
2393 userlog.action_logger(apiuser,
2392 userlog.action_logger(apiuser,
2394 'user_closed_pull_request:%s' % pull_request_id,
2393 'user_closed_pull_request:%s' % pull_request_id,
2395 pull_request.org_repo, request.ip_addr)
2394 pull_request.org_repo, request.ip_addr)
2396 meta.Session().commit()
2395 meta.Session().commit()
2397 return True
2396 return True
2398
2397
2399 # permission check inside
2398 # permission check inside
2400 def edit_reviewers(self, pull_request_id, add=None, remove=None):
2399 def edit_reviewers(self, pull_request_id, add=None, remove=None):
2401 """
2400 """
2402 Add and/or remove one or more reviewers to a pull request, by username
2401 Add and/or remove one or more reviewers to a pull request, by username
2403 or user ID. Reviewers are specified either as a single-user string or
2402 or user ID. Reviewers are specified either as a single-user string or
2404 as a JSON list of one or more strings.
2403 as a JSON list of one or more strings.
2405 """
2404 """
2406 if add is None and remove is None:
2405 if add is None and remove is None:
2407 raise JSONRPCError('''Invalid request. Neither 'add' nor 'remove' is specified.''')
2406 raise JSONRPCError('''Invalid request. Neither 'add' nor 'remove' is specified.''')
2408
2407
2409 pull_request = db.PullRequest.get(pull_request_id)
2408 pull_request = db.PullRequest.get(pull_request_id)
2410 if pull_request is None:
2409 if pull_request is None:
2411 raise JSONRPCError('pull request `%s` does not exist' % (pull_request_id,))
2410 raise JSONRPCError('pull request `%s` does not exist' % (pull_request_id,))
2412
2411
2413 apiuser = get_user_or_error(request.authuser.user_id)
2412 apiuser = get_user_or_error(request.authuser.user_id)
2414 is_owner = apiuser.user_id == pull_request.owner_id
2413 is_owner = apiuser.user_id == pull_request.owner_id
2415 is_repo_admin = HasRepoPermissionLevel('admin')(pull_request.other_repo.repo_name)
2414 is_repo_admin = HasRepoPermissionLevel('admin')(pull_request.other_repo.repo_name)
2416 if not (apiuser.admin or is_repo_admin or is_owner):
2415 if not (apiuser.admin or is_repo_admin or is_owner):
2417 raise JSONRPCError('No permission to edit reviewers of this pull request. User needs to be admin or pull request owner.')
2416 raise JSONRPCError('No permission to edit reviewers of this pull request. User needs to be admin or pull request owner.')
2418 if pull_request.is_closed():
2417 if pull_request.is_closed():
2419 raise JSONRPCError('Cannot edit reviewers of a closed pull request.')
2418 raise JSONRPCError('Cannot edit reviewers of a closed pull request.')
2420
2419
2421 if not isinstance(add, list):
2420 if not isinstance(add, list):
2422 add = [add]
2421 add = [add]
2423 if not isinstance(remove, list):
2422 if not isinstance(remove, list):
2424 remove = [remove]
2423 remove = [remove]
2425
2424
2426 # look up actual user objects from given name or id. Bail out if unknown.
2425 # look up actual user objects from given name or id. Bail out if unknown.
2427 add_objs = set(get_user_or_error(user) for user in add if user is not None)
2426 add_objs = set(get_user_or_error(user) for user in add if user is not None)
2428 remove_objs = set(get_user_or_error(user) for user in remove if user is not None)
2427 remove_objs = set(get_user_or_error(user) for user in remove if user is not None)
2429
2428
2430 new_reviewers = redundant_reviewers = set()
2429 new_reviewers = redundant_reviewers = set()
2431 if add_objs:
2430 if add_objs:
2432 new_reviewers, redundant_reviewers = PullRequestModel().add_reviewers(apiuser, pull_request, add_objs)
2431 new_reviewers, redundant_reviewers = PullRequestModel().add_reviewers(apiuser, pull_request, add_objs)
2433 if remove_objs:
2432 if remove_objs:
2434 PullRequestModel().remove_reviewers(apiuser, pull_request, remove_objs)
2433 PullRequestModel().remove_reviewers(apiuser, pull_request, remove_objs)
2435
2434
2436 meta.Session().commit()
2435 meta.Session().commit()
2437
2436
2438 return {
2437 return {
2439 'added': [x.username for x in new_reviewers],
2438 'added': [x.username for x in new_reviewers],
2440 'already_present': [x.username for x in redundant_reviewers],
2439 'already_present': [x.username for x in redundant_reviewers],
2441 # NOTE: no explicit check that removed reviewers were actually present.
2440 # NOTE: no explicit check that removed reviewers were actually present.
2442 'removed': [x.username for x in remove_objs],
2441 'removed': [x.username for x in remove_objs],
2443 }
2442 }
@@ -1,700 +1,698 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.repo
15 kallithea.model.repo
16 ~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~
17
17
18 Repository model for kallithea
18 Repository model for kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Jun 5, 2010
22 :created_on: Jun 5, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26
26
27 """
27 """
28
28
29 import logging
29 import logging
30 import os
30 import os
31 import shutil
31 import shutil
32 import traceback
32 import traceback
33 from datetime import datetime
33 from datetime import datetime
34
34
35 import kallithea.lib.utils2
35 import kallithea.lib.utils2
36 from kallithea.lib import hooks
36 from kallithea.lib import hooks
37 from kallithea.lib.auth import HasRepoPermissionLevel, HasUserGroupPermissionLevel
37 from kallithea.lib.auth import HasRepoPermissionLevel, HasUserGroupPermissionLevel
38 from kallithea.lib.exceptions import AttachedForksError
38 from kallithea.lib.exceptions import AttachedForksError
39 from kallithea.lib.utils import is_valid_repo_uri, make_ui
39 from kallithea.lib.utils import is_valid_repo_uri, make_ui
40 from kallithea.lib.utils2 import LazyProperty, get_current_authuser, obfuscate_url_pw, remove_prefix
40 from kallithea.lib.utils2 import LazyProperty, get_current_authuser, obfuscate_url_pw, remove_prefix
41 from kallithea.lib.vcs.backends import get_backend
41 from kallithea.lib.vcs.backends import get_backend
42 from kallithea.model import db, meta, scm
42 from kallithea.model import db, meta, scm
43
43
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class RepoModel(object):
48 class RepoModel(object):
49
49
50 def _create_default_perms(self, repository, private):
50 def _create_default_perms(self, repository, private):
51 # create default permission
51 # create default permission
52 default = 'repository.read'
52 default = 'repository.read'
53 def_user = db.User.get_default_user()
53 def_user = db.User.get_default_user()
54 for p in def_user.user_perms:
54 for p in def_user.user_perms:
55 if p.permission.permission_name.startswith('repository.'):
55 if p.permission.permission_name.startswith('repository.'):
56 default = p.permission.permission_name
56 default = p.permission.permission_name
57 break
57 break
58
58
59 default_perm = 'repository.none' if private else default
59 default_perm = 'repository.none' if private else default
60
60
61 repo_to_perm = db.UserRepoToPerm()
61 repo_to_perm = db.UserRepoToPerm()
62 repo_to_perm.permission = db.Permission.get_by_key(default_perm)
62 repo_to_perm.permission = db.Permission.get_by_key(default_perm)
63
63
64 repo_to_perm.repository = repository
64 repo_to_perm.repository = repository
65 repo_to_perm.user_id = def_user.user_id
65 repo_to_perm.user_id = def_user.user_id
66 meta.Session().add(repo_to_perm)
66 meta.Session().add(repo_to_perm)
67
67
68 return repo_to_perm
68 return repo_to_perm
69
69
70 @LazyProperty
70 @LazyProperty
71 def repos_path(self):
71 def repos_path(self):
72 """
72 """
73 Gets the repositories root path from database
73 Gets the repositories root path from database
74 """
74 """
75
75
76 q = db.Ui.query().filter(db.Ui.ui_key == '/').one()
76 q = db.Ui.query().filter(db.Ui.ui_key == '/').one()
77 return q.ui_value
77 return q.ui_value
78
78
79 def get(self, repo_id):
79 def get(self, repo_id):
80 repo = db.Repository.query() \
80 repo = db.Repository.query() \
81 .filter(db.Repository.repo_id == repo_id)
81 .filter(db.Repository.repo_id == repo_id)
82 return repo.scalar()
82 return repo.scalar()
83
83
84 def get_repo(self, repository):
84 def get_repo(self, repository):
85 return db.Repository.guess_instance(repository)
85 return db.Repository.guess_instance(repository)
86
86
87 def get_by_repo_name(self, repo_name):
87 def get_by_repo_name(self, repo_name):
88 repo = db.Repository.query() \
88 repo = db.Repository.query() \
89 .filter(db.Repository.repo_name == repo_name)
89 .filter(db.Repository.repo_name == repo_name)
90 return repo.scalar()
90 return repo.scalar()
91
91
92 @classmethod
92 @classmethod
93 def _render_datatable(cls, tmpl, *args, **kwargs):
93 def _render_datatable(cls, tmpl, *args, **kwargs):
94 from tg import app_globals, request
94 from tg import app_globals, request
95 from tg import tmpl_context as c
95 from tg import tmpl_context as c
96 from tg.i18n import ugettext as _
96 from tg.i18n import ugettext as _
97
97
98 import kallithea.lib.helpers as h
98 import kallithea.lib.helpers as h
99
99
100 _tmpl_lookup = app_globals.mako_lookup
100 _tmpl_lookup = app_globals.mako_lookup
101 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
101 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
102
102
103 tmpl = template.get_def(tmpl)
103 tmpl = template.get_def(tmpl)
104 kwargs.update(dict(_=_, h=h, c=c, request=request))
104 kwargs.update(dict(_=_, h=h, c=c, request=request))
105 return tmpl.render_unicode(*args, **kwargs)
105 return tmpl.render_unicode(*args, **kwargs)
106
106
107 def get_repos_as_dict(self, repos_list, repo_groups_list=None,
107 def get_repos_as_dict(self, repos_list, repo_groups_list=None,
108 admin=False,
108 admin=False,
109 short_name=False):
109 short_name=False):
110 """Return repository list for use by DataTable.
110 """Return repository list for use by DataTable.
111 repos_list: list of repositories - but will be filtered for read permission.
111 repos_list: list of repositories - but will be filtered for read permission.
112 repo_groups_list: added at top of list without permission check.
112 repo_groups_list: added at top of list without permission check.
113 admin: return data for action column.
113 admin: return data for action column.
114 """
114 """
115 _render = self._render_datatable
115 _render = self._render_datatable
116 from tg import request
116 from tg import request
117 from tg import tmpl_context as c
117 from tg import tmpl_context as c
118
118
119 import kallithea.lib.helpers as h
119 import kallithea.lib.helpers as h
120
120
121 def repo_lnk(name, rtype, rstate, private, fork_of):
121 def repo_lnk(name, rtype, rstate, private, fork_of):
122 return _render('repo_name', name, rtype, rstate, private, fork_of,
122 return _render('repo_name', name, rtype, rstate, private, fork_of,
123 short_name=short_name)
123 short_name=short_name)
124
124
125 def following(repo_id, is_following):
125 def following(repo_id, is_following):
126 return _render('following', repo_id, is_following)
126 return _render('following', repo_id, is_following)
127
127
128 def last_change(last_change):
128 def last_change(last_change):
129 return _render("last_change", last_change)
129 return _render("last_change", last_change)
130
130
131 def rss_lnk(repo_name):
131 def rss_lnk(repo_name):
132 return _render("rss", repo_name)
132 return _render("rss", repo_name)
133
133
134 def atom_lnk(repo_name):
134 def atom_lnk(repo_name):
135 return _render("atom", repo_name)
135 return _render("atom", repo_name)
136
136
137 def last_rev(repo_name, cs_cache):
137 def last_rev(repo_name, cs_cache):
138 return _render('revision', repo_name, cs_cache.get('revision'),
138 return _render('revision', repo_name, cs_cache.get('revision'),
139 cs_cache.get('raw_id'), cs_cache.get('author'),
139 cs_cache.get('raw_id'), cs_cache.get('author'),
140 cs_cache.get('message'))
140 cs_cache.get('message'))
141
141
142 def desc(desc):
142 def desc(desc):
143 import kallithea.lib.helpers as h
143 import kallithea.lib.helpers as h
144 return h.urlify_text(desc, truncate=80, stylize=c.visual.stylify_metalabels)
144 return h.urlify_text(desc, truncate=80, stylize=c.visual.stylify_metalabels)
145
145
146 def state(repo_state):
146 def state(repo_state):
147 return _render("repo_state", repo_state)
147 return _render("repo_state", repo_state)
148
148
149 def repo_actions(repo_name):
149 def repo_actions(repo_name):
150 return _render('repo_actions', repo_name)
150 return _render('repo_actions', repo_name)
151
151
152 def owner_actions(owner_id, username):
152 def owner_actions(owner_id, username):
153 return _render('user_name', owner_id, username)
153 return _render('user_name', owner_id, username)
154
154
155 repos_data = []
155 repos_data = []
156
156
157 for gr in repo_groups_list or []:
157 for gr in repo_groups_list or []:
158 repos_data.append(dict(
158 repos_data.append(dict(
159 raw_name='\0' + gr.name, # sort before repositories
159 raw_name='\0' + gr.name, # sort before repositories
160 just_name=gr.name,
160 just_name=gr.name,
161 name=_render('group_name_html', group_name=gr.group_name, name=gr.name),
161 name=_render('group_name_html', group_name=gr.group_name, name=gr.name),
162 desc=gr.group_description))
162 desc=gr.group_description))
163
163
164 for repo in repos_list:
164 for repo in repos_list:
165 if not HasRepoPermissionLevel('read')(repo.repo_name, 'get_repos_as_dict check'):
165 if not HasRepoPermissionLevel('read')(repo.repo_name, 'get_repos_as_dict check'):
166 continue
166 continue
167 cs_cache = repo.changeset_cache
167 cs_cache = repo.changeset_cache
168 row = {
168 row = {
169 "raw_name": repo.repo_name,
169 "raw_name": repo.repo_name,
170 "just_name": repo.just_name,
170 "just_name": repo.just_name,
171 "name": repo_lnk(repo.repo_name, repo.repo_type,
171 "name": repo_lnk(repo.repo_name, repo.repo_type,
172 repo.repo_state, repo.private, repo.fork),
172 repo.repo_state, repo.private, repo.fork),
173 "following": following(
173 "following": following(
174 repo.repo_id,
174 repo.repo_id,
175 scm.ScmModel().is_following_repo(repo.repo_name, request.authuser.user_id),
175 scm.ScmModel().is_following_repo(repo.repo_name, request.authuser.user_id),
176 ),
176 ),
177 "last_change_iso": repo.last_db_change.isoformat(),
177 "last_change_iso": repo.last_db_change.isoformat(),
178 "last_change": last_change(repo.last_db_change),
178 "last_change": last_change(repo.last_db_change),
179 "last_changeset": last_rev(repo.repo_name, cs_cache),
179 "last_changeset": last_rev(repo.repo_name, cs_cache),
180 "last_rev_raw": cs_cache.get('revision'),
180 "last_rev_raw": cs_cache.get('revision'),
181 "desc": desc(repo.description),
181 "desc": desc(repo.description),
182 "owner": h.person(repo.owner),
182 "owner": h.person(repo.owner),
183 "state": state(repo.repo_state),
183 "state": state(repo.repo_state),
184 "rss": rss_lnk(repo.repo_name),
184 "rss": rss_lnk(repo.repo_name),
185 "atom": atom_lnk(repo.repo_name),
185 "atom": atom_lnk(repo.repo_name),
186 }
186 }
187 if admin:
187 if admin:
188 row.update({
188 row.update({
189 "action": repo_actions(repo.repo_name),
189 "action": repo_actions(repo.repo_name),
190 "owner": owner_actions(repo.owner_id,
190 "owner": owner_actions(repo.owner_id,
191 h.person(repo.owner))
191 h.person(repo.owner))
192 })
192 })
193 repos_data.append(row)
193 repos_data.append(row)
194
194
195 return {
195 return {
196 "sort": "name",
196 "sort": "name",
197 "dir": "asc",
197 "dir": "asc",
198 "records": repos_data
198 "records": repos_data
199 }
199 }
200
200
201 def _get_defaults(self, repo_name):
201 def _get_defaults(self, repo_name):
202 """
202 """
203 Gets information about repository, and returns a dict for
203 Gets information about repository, and returns a dict for
204 usage in forms
204 usage in forms
205
205
206 :param repo_name:
206 :param repo_name:
207 """
207 """
208
208
209 repo_info = db.Repository.get_by_repo_name(repo_name)
209 repo_info = db.Repository.get_by_repo_name(repo_name)
210
210
211 if repo_info is None:
211 if repo_info is None:
212 return None
212 return None
213
213
214 defaults = repo_info.get_dict()
214 defaults = repo_info.get_dict()
215 defaults['repo_name'] = repo_info.just_name
215 defaults['repo_name'] = repo_info.just_name
216 defaults['repo_group'] = repo_info.group_id
216 defaults['repo_group'] = repo_info.group_id
217
217
218 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
218 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
219 (1, 'repo_description'),
219 (1, 'repo_description'),
220 (1, 'repo_landing_rev'), (0, 'clone_uri'),
220 (1, 'repo_landing_rev'), (0, 'clone_uri'),
221 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
221 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
222 attr = k
222 attr = k
223 if strip:
223 if strip:
224 attr = remove_prefix(k, 'repo_')
224 attr = remove_prefix(k, 'repo_')
225
225
226 val = defaults[attr]
226 val = defaults[attr]
227 if k == 'repo_landing_rev':
227 if k == 'repo_landing_rev':
228 val = ':'.join(defaults[attr])
228 val = ':'.join(defaults[attr])
229 defaults[k] = val
229 defaults[k] = val
230 if k == 'clone_uri':
230 if k == 'clone_uri':
231 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
231 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
232
232
233 # fill owner
233 # fill owner
234 if repo_info.owner:
234 if repo_info.owner:
235 defaults.update({'owner': repo_info.owner.username})
235 defaults.update({'owner': repo_info.owner.username})
236 else:
236 else:
237 replacement_user = db.User.query().filter(db.User.admin ==
237 replacement_user = db.User.query().filter(db.User.admin ==
238 True).first().username
238 True).first().username
239 defaults.update({'owner': replacement_user})
239 defaults.update({'owner': replacement_user})
240
240
241 # fill repository users
241 # fill repository users
242 for p in repo_info.repo_to_perm:
242 for p in repo_info.repo_to_perm:
243 defaults.update({'u_perm_%s' % p.user.username:
243 defaults.update({'u_perm_%s' % p.user.username:
244 p.permission.permission_name})
244 p.permission.permission_name})
245
245
246 # fill repository groups
246 # fill repository groups
247 for p in repo_info.users_group_to_perm:
247 for p in repo_info.users_group_to_perm:
248 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
248 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
249 p.permission.permission_name})
249 p.permission.permission_name})
250
250
251 return defaults
251 return defaults
252
252
253 def update(self, repo, **kwargs):
253 def update(self, repo, **kwargs):
254 try:
254 try:
255 cur_repo = db.Repository.guess_instance(repo)
255 cur_repo = db.Repository.guess_instance(repo)
256 org_repo_name = cur_repo.repo_name
256 org_repo_name = cur_repo.repo_name
257 if 'owner' in kwargs:
257 if 'owner' in kwargs:
258 cur_repo.owner = db.User.get_by_username(kwargs['owner'])
258 cur_repo.owner = db.User.get_by_username(kwargs['owner'])
259
259
260 if 'repo_group' in kwargs:
260 if 'repo_group' in kwargs:
261 assert kwargs['repo_group'] != '-1', kwargs # RepoForm should have converted to None
261 assert kwargs['repo_group'] != '-1', kwargs # RepoForm should have converted to None
262 cur_repo.group = db.RepoGroup.get(kwargs['repo_group'])
262 cur_repo.group = db.RepoGroup.get(kwargs['repo_group'])
263 cur_repo.repo_name = cur_repo.get_new_name(cur_repo.just_name)
263 cur_repo.repo_name = cur_repo.get_new_name(cur_repo.just_name)
264 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
264 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
265 for k in ['repo_enable_downloads',
265 for k in ['repo_enable_downloads',
266 'repo_description',
266 'repo_description',
267 'repo_landing_rev',
267 'repo_landing_rev',
268 'repo_private',
268 'repo_private',
269 'repo_enable_statistics',
269 'repo_enable_statistics',
270 ]:
270 ]:
271 if k in kwargs:
271 if k in kwargs:
272 setattr(cur_repo, remove_prefix(k, 'repo_'), kwargs[k])
272 setattr(cur_repo, remove_prefix(k, 'repo_'), kwargs[k])
273 clone_uri = kwargs.get('clone_uri')
273 clone_uri = kwargs.get('clone_uri')
274 if clone_uri is not None and clone_uri != cur_repo.clone_uri_hidden:
274 if clone_uri is not None and clone_uri != cur_repo.clone_uri_hidden:
275 # clone_uri is modified - if given a value, check it is valid
275 # clone_uri is modified - if given a value, check it is valid
276 if clone_uri != '':
276 if clone_uri != '':
277 # will raise exception on error
277 # will raise exception on error
278 is_valid_repo_uri(cur_repo.repo_type, clone_uri, make_ui())
278 is_valid_repo_uri(cur_repo.repo_type, clone_uri, make_ui())
279 cur_repo.clone_uri = clone_uri
279 cur_repo.clone_uri = clone_uri
280
280
281 if 'repo_name' in kwargs:
281 if 'repo_name' in kwargs:
282 repo_name = kwargs['repo_name']
282 repo_name = kwargs['repo_name']
283 if kallithea.lib.utils2.repo_name_slug(repo_name) != repo_name:
283 if kallithea.lib.utils2.repo_name_slug(repo_name) != repo_name:
284 raise Exception('invalid repo name %s' % repo_name)
284 raise Exception('invalid repo name %s' % repo_name)
285 cur_repo.repo_name = cur_repo.get_new_name(repo_name)
285 cur_repo.repo_name = cur_repo.get_new_name(repo_name)
286
286
287 # if private flag is set, reset default permission to NONE
287 # if private flag is set, reset default permission to NONE
288 if kwargs.get('repo_private'):
288 if kwargs.get('repo_private'):
289 EMPTY_PERM = 'repository.none'
289 EMPTY_PERM = 'repository.none'
290 RepoModel().grant_user_permission(
290 RepoModel().grant_user_permission(
291 repo=cur_repo, user='default', perm=EMPTY_PERM
291 repo=cur_repo, user='default', perm=EMPTY_PERM
292 )
292 )
293 # handle extra fields
293 # handle extra fields
294 for field in [k for k in kwargs if k.startswith(db.RepositoryField.PREFIX)]:
294 for field in [k for k in kwargs if k.startswith(db.RepositoryField.PREFIX)]:
295 k = db.RepositoryField.un_prefix_key(field)
295 k = db.RepositoryField.un_prefix_key(field)
296 ex_field = db.RepositoryField.get_by_key_name(key=k, repo=cur_repo)
296 ex_field = db.RepositoryField.get_by_key_name(key=k, repo=cur_repo)
297 if ex_field:
297 if ex_field:
298 ex_field.field_value = kwargs[field]
298 ex_field.field_value = kwargs[field]
299
299
300 if org_repo_name != cur_repo.repo_name:
300 if org_repo_name != cur_repo.repo_name:
301 # rename repository
301 # rename repository
302 self._rename_filesystem_repo(old=org_repo_name, new=cur_repo.repo_name)
302 self._rename_filesystem_repo(old=org_repo_name, new=cur_repo.repo_name)
303
303
304 return cur_repo
304 return cur_repo
305 except Exception:
305 except Exception:
306 log.error(traceback.format_exc())
306 log.error(traceback.format_exc())
307 raise
307 raise
308
308
309 def _create_repo(self, repo_name, repo_type, description, owner,
309 def _create_repo(self, repo_name, repo_type, description, owner,
310 private=False, clone_uri=None, repo_group=None,
310 private=False, clone_uri=None, repo_group=None,
311 landing_rev='rev:tip', fork_of=None,
311 landing_rev='rev:tip', fork_of=None,
312 copy_fork_permissions=False, enable_statistics=False,
312 copy_fork_permissions=False, enable_statistics=False,
313 enable_downloads=False,
313 enable_downloads=False,
314 copy_group_permissions=False, state=db.Repository.STATE_PENDING):
314 copy_group_permissions=False, state=db.Repository.STATE_PENDING):
315 """
315 """
316 Create repository inside database with PENDING state. This should only be
316 Create repository inside database with PENDING state. This should only be
317 executed by create() repo, with exception of importing existing repos.
317 executed by create() repo, with exception of importing existing repos.
318
318
319 """
319 """
320 owner = db.User.guess_instance(owner)
320 owner = db.User.guess_instance(owner)
321 fork_of = db.Repository.guess_instance(fork_of)
321 fork_of = db.Repository.guess_instance(fork_of)
322 repo_group = db.RepoGroup.guess_instance(repo_group)
322 repo_group = db.RepoGroup.guess_instance(repo_group)
323 try:
323 try:
324 repo_name = repo_name
325 description = description
326 # repo name is just a name of repository
324 # repo name is just a name of repository
327 # while repo_name_full is a full qualified name that is combined
325 # while repo_name_full is a full qualified name that is combined
328 # with name and path of group
326 # with name and path of group
329 repo_name_full = repo_name
327 repo_name_full = repo_name
330 repo_name = repo_name.split(kallithea.URL_SEP)[-1]
328 repo_name = repo_name.split(kallithea.URL_SEP)[-1]
331 if kallithea.lib.utils2.repo_name_slug(repo_name) != repo_name:
329 if kallithea.lib.utils2.repo_name_slug(repo_name) != repo_name:
332 raise Exception('invalid repo name %s' % repo_name)
330 raise Exception('invalid repo name %s' % repo_name)
333
331
334 new_repo = db.Repository()
332 new_repo = db.Repository()
335 new_repo.repo_state = state
333 new_repo.repo_state = state
336 new_repo.enable_statistics = False
334 new_repo.enable_statistics = False
337 new_repo.repo_name = repo_name_full
335 new_repo.repo_name = repo_name_full
338 new_repo.repo_type = repo_type
336 new_repo.repo_type = repo_type
339 new_repo.owner = owner
337 new_repo.owner = owner
340 new_repo.group = repo_group
338 new_repo.group = repo_group
341 new_repo.description = description or repo_name
339 new_repo.description = description or repo_name
342 new_repo.private = private
340 new_repo.private = private
343 if clone_uri:
341 if clone_uri:
344 # will raise exception on error
342 # will raise exception on error
345 is_valid_repo_uri(repo_type, clone_uri, make_ui())
343 is_valid_repo_uri(repo_type, clone_uri, make_ui())
346 new_repo.clone_uri = clone_uri
344 new_repo.clone_uri = clone_uri
347 new_repo.landing_rev = landing_rev
345 new_repo.landing_rev = landing_rev
348
346
349 new_repo.enable_statistics = enable_statistics
347 new_repo.enable_statistics = enable_statistics
350 new_repo.enable_downloads = enable_downloads
348 new_repo.enable_downloads = enable_downloads
351
349
352 if fork_of:
350 if fork_of:
353 parent_repo = fork_of
351 parent_repo = fork_of
354 new_repo.fork = parent_repo
352 new_repo.fork = parent_repo
355
353
356 meta.Session().add(new_repo)
354 meta.Session().add(new_repo)
357
355
358 if fork_of and copy_fork_permissions:
356 if fork_of and copy_fork_permissions:
359 repo = fork_of
357 repo = fork_of
360 user_perms = db.UserRepoToPerm.query() \
358 user_perms = db.UserRepoToPerm.query() \
361 .filter(db.UserRepoToPerm.repository == repo).all()
359 .filter(db.UserRepoToPerm.repository == repo).all()
362 group_perms = db.UserGroupRepoToPerm.query() \
360 group_perms = db.UserGroupRepoToPerm.query() \
363 .filter(db.UserGroupRepoToPerm.repository == repo).all()
361 .filter(db.UserGroupRepoToPerm.repository == repo).all()
364
362
365 for perm in user_perms:
363 for perm in user_perms:
366 db.UserRepoToPerm.create(perm.user, new_repo, perm.permission)
364 db.UserRepoToPerm.create(perm.user, new_repo, perm.permission)
367
365
368 for perm in group_perms:
366 for perm in group_perms:
369 db.UserGroupRepoToPerm.create(perm.users_group, new_repo,
367 db.UserGroupRepoToPerm.create(perm.users_group, new_repo,
370 perm.permission)
368 perm.permission)
371
369
372 elif repo_group and copy_group_permissions:
370 elif repo_group and copy_group_permissions:
373
371
374 user_perms = db.UserRepoGroupToPerm.query() \
372 user_perms = db.UserRepoGroupToPerm.query() \
375 .filter(db.UserRepoGroupToPerm.group == repo_group).all()
373 .filter(db.UserRepoGroupToPerm.group == repo_group).all()
376
374
377 group_perms = db.UserGroupRepoGroupToPerm.query() \
375 group_perms = db.UserGroupRepoGroupToPerm.query() \
378 .filter(db.UserGroupRepoGroupToPerm.group == repo_group).all()
376 .filter(db.UserGroupRepoGroupToPerm.group == repo_group).all()
379
377
380 for perm in user_perms:
378 for perm in user_perms:
381 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
379 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
382 perm_obj = db.Permission.get_by_key(perm_name)
380 perm_obj = db.Permission.get_by_key(perm_name)
383 db.UserRepoToPerm.create(perm.user, new_repo, perm_obj)
381 db.UserRepoToPerm.create(perm.user, new_repo, perm_obj)
384
382
385 for perm in group_perms:
383 for perm in group_perms:
386 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
384 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
387 perm_obj = db.Permission.get_by_key(perm_name)
385 perm_obj = db.Permission.get_by_key(perm_name)
388 db.UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
386 db.UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
389
387
390 else:
388 else:
391 self._create_default_perms(new_repo, private)
389 self._create_default_perms(new_repo, private)
392
390
393 # now automatically start following this repository as owner
391 # now automatically start following this repository as owner
394 scm.ScmModel().toggle_following_repo(new_repo.repo_id, owner.user_id)
392 scm.ScmModel().toggle_following_repo(new_repo.repo_id, owner.user_id)
395 # we need to flush here, in order to check if database won't
393 # we need to flush here, in order to check if database won't
396 # throw any exceptions, create filesystem dirs at the very end
394 # throw any exceptions, create filesystem dirs at the very end
397 meta.Session().flush()
395 meta.Session().flush()
398 return new_repo
396 return new_repo
399 except Exception:
397 except Exception:
400 log.error(traceback.format_exc())
398 log.error(traceback.format_exc())
401 raise
399 raise
402
400
403 def create(self, form_data, cur_user):
401 def create(self, form_data, cur_user):
404 """
402 """
405 Create repository using celery tasks
403 Create repository using celery tasks
406
404
407 :param form_data:
405 :param form_data:
408 :param cur_user:
406 :param cur_user:
409 """
407 """
410 from kallithea.lib.celerylib import tasks
408 from kallithea.lib.celerylib import tasks
411 return tasks.create_repo(form_data, cur_user)
409 return tasks.create_repo(form_data, cur_user)
412
410
413 def _update_permissions(self, repo, perms_new=None, perms_updates=None,
411 def _update_permissions(self, repo, perms_new=None, perms_updates=None,
414 check_perms=True):
412 check_perms=True):
415 if not perms_new:
413 if not perms_new:
416 perms_new = []
414 perms_new = []
417 if not perms_updates:
415 if not perms_updates:
418 perms_updates = []
416 perms_updates = []
419
417
420 # update permissions
418 # update permissions
421 for member, perm, member_type in perms_updates:
419 for member, perm, member_type in perms_updates:
422 if member_type == 'user':
420 if member_type == 'user':
423 # this updates existing one
421 # this updates existing one
424 self.grant_user_permission(
422 self.grant_user_permission(
425 repo=repo, user=member, perm=perm
423 repo=repo, user=member, perm=perm
426 )
424 )
427 else:
425 else:
428 # check if we have permissions to alter this usergroup's access
426 # check if we have permissions to alter this usergroup's access
429 if not check_perms or HasUserGroupPermissionLevel('read')(member):
427 if not check_perms or HasUserGroupPermissionLevel('read')(member):
430 self.grant_user_group_permission(
428 self.grant_user_group_permission(
431 repo=repo, group_name=member, perm=perm
429 repo=repo, group_name=member, perm=perm
432 )
430 )
433 # set new permissions
431 # set new permissions
434 for member, perm, member_type in perms_new:
432 for member, perm, member_type in perms_new:
435 if member_type == 'user':
433 if member_type == 'user':
436 self.grant_user_permission(
434 self.grant_user_permission(
437 repo=repo, user=member, perm=perm
435 repo=repo, user=member, perm=perm
438 )
436 )
439 else:
437 else:
440 # check if we have permissions to alter this usergroup's access
438 # check if we have permissions to alter this usergroup's access
441 if not check_perms or HasUserGroupPermissionLevel('read')(member):
439 if not check_perms or HasUserGroupPermissionLevel('read')(member):
442 self.grant_user_group_permission(
440 self.grant_user_group_permission(
443 repo=repo, group_name=member, perm=perm
441 repo=repo, group_name=member, perm=perm
444 )
442 )
445
443
446 def create_fork(self, form_data, cur_user):
444 def create_fork(self, form_data, cur_user):
447 """
445 """
448 Simple wrapper into executing celery task for fork creation
446 Simple wrapper into executing celery task for fork creation
449
447
450 :param form_data:
448 :param form_data:
451 :param cur_user:
449 :param cur_user:
452 """
450 """
453 from kallithea.lib.celerylib import tasks
451 from kallithea.lib.celerylib import tasks
454 return tasks.create_repo_fork(form_data, cur_user)
452 return tasks.create_repo_fork(form_data, cur_user)
455
453
456 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
454 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
457 """
455 """
458 Delete given repository, forks parameter defines what do do with
456 Delete given repository, forks parameter defines what do do with
459 attached forks. Throws AttachedForksError if deleted repo has attached
457 attached forks. Throws AttachedForksError if deleted repo has attached
460 forks
458 forks
461
459
462 :param repo:
460 :param repo:
463 :param forks: str 'delete' or 'detach'
461 :param forks: str 'delete' or 'detach'
464 :param fs_remove: remove(archive) repo from filesystem
462 :param fs_remove: remove(archive) repo from filesystem
465 """
463 """
466 if not cur_user:
464 if not cur_user:
467 cur_user = getattr(get_current_authuser(), 'username', None)
465 cur_user = getattr(get_current_authuser(), 'username', None)
468 repo = db.Repository.guess_instance(repo)
466 repo = db.Repository.guess_instance(repo)
469 if repo is not None:
467 if repo is not None:
470 if forks == 'detach':
468 if forks == 'detach':
471 for r in repo.forks:
469 for r in repo.forks:
472 r.fork = None
470 r.fork = None
473 elif forks == 'delete':
471 elif forks == 'delete':
474 for r in repo.forks:
472 for r in repo.forks:
475 self.delete(r, forks='delete')
473 self.delete(r, forks='delete')
476 elif [f for f in repo.forks]:
474 elif [f for f in repo.forks]:
477 raise AttachedForksError()
475 raise AttachedForksError()
478
476
479 old_repo_dict = repo.get_dict()
477 old_repo_dict = repo.get_dict()
480 try:
478 try:
481 meta.Session().delete(repo)
479 meta.Session().delete(repo)
482 if fs_remove:
480 if fs_remove:
483 self._delete_filesystem_repo(repo)
481 self._delete_filesystem_repo(repo)
484 else:
482 else:
485 log.debug('skipping removal from filesystem')
483 log.debug('skipping removal from filesystem')
486 hooks.log_delete_repository(old_repo_dict,
484 hooks.log_delete_repository(old_repo_dict,
487 deleted_by=cur_user)
485 deleted_by=cur_user)
488 except Exception:
486 except Exception:
489 log.error(traceback.format_exc())
487 log.error(traceback.format_exc())
490 raise
488 raise
491
489
492 def grant_user_permission(self, repo, user, perm):
490 def grant_user_permission(self, repo, user, perm):
493 """
491 """
494 Grant permission for user on given repository, or update existing one
492 Grant permission for user on given repository, or update existing one
495 if found
493 if found
496
494
497 :param repo: Instance of Repository, repository_id, or repository name
495 :param repo: Instance of Repository, repository_id, or repository name
498 :param user: Instance of User, user_id or username
496 :param user: Instance of User, user_id or username
499 :param perm: Instance of Permission, or permission_name
497 :param perm: Instance of Permission, or permission_name
500 """
498 """
501 user = db.User.guess_instance(user)
499 user = db.User.guess_instance(user)
502 repo = db.Repository.guess_instance(repo)
500 repo = db.Repository.guess_instance(repo)
503 permission = db.Permission.guess_instance(perm)
501 permission = db.Permission.guess_instance(perm)
504
502
505 # check if we have that permission already
503 # check if we have that permission already
506 obj = db.UserRepoToPerm.query() \
504 obj = db.UserRepoToPerm.query() \
507 .filter(db.UserRepoToPerm.user == user) \
505 .filter(db.UserRepoToPerm.user == user) \
508 .filter(db.UserRepoToPerm.repository == repo) \
506 .filter(db.UserRepoToPerm.repository == repo) \
509 .scalar()
507 .scalar()
510 if obj is None:
508 if obj is None:
511 # create new !
509 # create new !
512 obj = db.UserRepoToPerm()
510 obj = db.UserRepoToPerm()
513 meta.Session().add(obj)
511 meta.Session().add(obj)
514 obj.repository = repo
512 obj.repository = repo
515 obj.user = user
513 obj.user = user
516 obj.permission = permission
514 obj.permission = permission
517 log.debug('Granted perm %s to %s on %s', perm, user, repo)
515 log.debug('Granted perm %s to %s on %s', perm, user, repo)
518 return obj
516 return obj
519
517
520 def revoke_user_permission(self, repo, user):
518 def revoke_user_permission(self, repo, user):
521 """
519 """
522 Revoke permission for user on given repository
520 Revoke permission for user on given repository
523
521
524 :param repo: Instance of Repository, repository_id, or repository name
522 :param repo: Instance of Repository, repository_id, or repository name
525 :param user: Instance of User, user_id or username
523 :param user: Instance of User, user_id or username
526 """
524 """
527
525
528 user = db.User.guess_instance(user)
526 user = db.User.guess_instance(user)
529 repo = db.Repository.guess_instance(repo)
527 repo = db.Repository.guess_instance(repo)
530
528
531 obj = db.UserRepoToPerm.query() \
529 obj = db.UserRepoToPerm.query() \
532 .filter(db.UserRepoToPerm.repository == repo) \
530 .filter(db.UserRepoToPerm.repository == repo) \
533 .filter(db.UserRepoToPerm.user == user) \
531 .filter(db.UserRepoToPerm.user == user) \
534 .scalar()
532 .scalar()
535 if obj is not None:
533 if obj is not None:
536 meta.Session().delete(obj)
534 meta.Session().delete(obj)
537 log.debug('Revoked perm on %s on %s', repo, user)
535 log.debug('Revoked perm on %s on %s', repo, user)
538
536
539 def grant_user_group_permission(self, repo, group_name, perm):
537 def grant_user_group_permission(self, repo, group_name, perm):
540 """
538 """
541 Grant permission for user group on given repository, or update
539 Grant permission for user group on given repository, or update
542 existing one if found
540 existing one if found
543
541
544 :param repo: Instance of Repository, repository_id, or repository name
542 :param repo: Instance of Repository, repository_id, or repository name
545 :param group_name: Instance of UserGroup, users_group_id,
543 :param group_name: Instance of UserGroup, users_group_id,
546 or user group name
544 or user group name
547 :param perm: Instance of Permission, or permission_name
545 :param perm: Instance of Permission, or permission_name
548 """
546 """
549 repo = db.Repository.guess_instance(repo)
547 repo = db.Repository.guess_instance(repo)
550 group_name = db.UserGroup.guess_instance(group_name)
548 group_name = db.UserGroup.guess_instance(group_name)
551 permission = db.Permission.guess_instance(perm)
549 permission = db.Permission.guess_instance(perm)
552
550
553 # check if we have that permission already
551 # check if we have that permission already
554 obj = db.UserGroupRepoToPerm.query() \
552 obj = db.UserGroupRepoToPerm.query() \
555 .filter(db.UserGroupRepoToPerm.users_group == group_name) \
553 .filter(db.UserGroupRepoToPerm.users_group == group_name) \
556 .filter(db.UserGroupRepoToPerm.repository == repo) \
554 .filter(db.UserGroupRepoToPerm.repository == repo) \
557 .scalar()
555 .scalar()
558
556
559 if obj is None:
557 if obj is None:
560 # create new
558 # create new
561 obj = db.UserGroupRepoToPerm()
559 obj = db.UserGroupRepoToPerm()
562 meta.Session().add(obj)
560 meta.Session().add(obj)
563
561
564 obj.repository = repo
562 obj.repository = repo
565 obj.users_group = group_name
563 obj.users_group = group_name
566 obj.permission = permission
564 obj.permission = permission
567 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
565 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
568 return obj
566 return obj
569
567
570 def revoke_user_group_permission(self, repo, group_name):
568 def revoke_user_group_permission(self, repo, group_name):
571 """
569 """
572 Revoke permission for user group on given repository
570 Revoke permission for user group on given repository
573
571
574 :param repo: Instance of Repository, repository_id, or repository name
572 :param repo: Instance of Repository, repository_id, or repository name
575 :param group_name: Instance of UserGroup, users_group_id,
573 :param group_name: Instance of UserGroup, users_group_id,
576 or user group name
574 or user group name
577 """
575 """
578 repo = db.Repository.guess_instance(repo)
576 repo = db.Repository.guess_instance(repo)
579 group_name = db.UserGroup.guess_instance(group_name)
577 group_name = db.UserGroup.guess_instance(group_name)
580
578
581 obj = db.UserGroupRepoToPerm.query() \
579 obj = db.UserGroupRepoToPerm.query() \
582 .filter(db.UserGroupRepoToPerm.repository == repo) \
580 .filter(db.UserGroupRepoToPerm.repository == repo) \
583 .filter(db.UserGroupRepoToPerm.users_group == group_name) \
581 .filter(db.UserGroupRepoToPerm.users_group == group_name) \
584 .scalar()
582 .scalar()
585 if obj is not None:
583 if obj is not None:
586 meta.Session().delete(obj)
584 meta.Session().delete(obj)
587 log.debug('Revoked perm to %s on %s', repo, group_name)
585 log.debug('Revoked perm to %s on %s', repo, group_name)
588
586
589 def delete_stats(self, repo_name):
587 def delete_stats(self, repo_name):
590 """
588 """
591 removes stats for given repo
589 removes stats for given repo
592
590
593 :param repo_name:
591 :param repo_name:
594 """
592 """
595 repo = db.Repository.guess_instance(repo_name)
593 repo = db.Repository.guess_instance(repo_name)
596 try:
594 try:
597 obj = db.Statistics.query() \
595 obj = db.Statistics.query() \
598 .filter(db.Statistics.repository == repo).scalar()
596 .filter(db.Statistics.repository == repo).scalar()
599 if obj is not None:
597 if obj is not None:
600 meta.Session().delete(obj)
598 meta.Session().delete(obj)
601 except Exception:
599 except Exception:
602 log.error(traceback.format_exc())
600 log.error(traceback.format_exc())
603 raise
601 raise
604
602
605 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
603 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
606 clone_uri=None, repo_store_location=None):
604 clone_uri=None, repo_store_location=None):
607 """
605 """
608 Makes repository on filesystem. Operation is group aware, meaning that it will create
606 Makes repository on filesystem. Operation is group aware, meaning that it will create
609 a repository within a group, and alter the paths accordingly to the group location.
607 a repository within a group, and alter the paths accordingly to the group location.
610
608
611 Note: clone_uri is low level and not validated - it might be a file system path used for validated cloning
609 Note: clone_uri is low level and not validated - it might be a file system path used for validated cloning
612 """
610 """
613 from kallithea.lib.utils import is_valid_repo, is_valid_repo_group
611 from kallithea.lib.utils import is_valid_repo, is_valid_repo_group
614
612
615 if '/' in repo_name:
613 if '/' in repo_name:
616 raise ValueError('repo_name must not contain groups got `%s`' % repo_name)
614 raise ValueError('repo_name must not contain groups got `%s`' % repo_name)
617
615
618 if isinstance(repo_group, db.RepoGroup):
616 if isinstance(repo_group, db.RepoGroup):
619 new_parent_path = os.sep.join(repo_group.full_path_splitted)
617 new_parent_path = os.sep.join(repo_group.full_path_splitted)
620 else:
618 else:
621 new_parent_path = repo_group or ''
619 new_parent_path = repo_group or ''
622
620
623 if repo_store_location:
621 if repo_store_location:
624 _paths = [repo_store_location]
622 _paths = [repo_store_location]
625 else:
623 else:
626 _paths = [self.repos_path, new_parent_path, repo_name]
624 _paths = [self.repos_path, new_parent_path, repo_name]
627 repo_path = os.path.join(*_paths)
625 repo_path = os.path.join(*_paths)
628
626
629 # check if this path is not a repository
627 # check if this path is not a repository
630 if is_valid_repo(repo_path, self.repos_path):
628 if is_valid_repo(repo_path, self.repos_path):
631 raise Exception('This path %s is a valid repository' % repo_path)
629 raise Exception('This path %s is a valid repository' % repo_path)
632
630
633 # check if this path is a group
631 # check if this path is a group
634 if is_valid_repo_group(repo_path, self.repos_path):
632 if is_valid_repo_group(repo_path, self.repos_path):
635 raise Exception('This path %s is a valid group' % repo_path)
633 raise Exception('This path %s is a valid group' % repo_path)
636
634
637 log.info('creating repo %s in %s from url: `%s`',
635 log.info('creating repo %s in %s from url: `%s`',
638 repo_name, repo_path,
636 repo_name, repo_path,
639 obfuscate_url_pw(clone_uri))
637 obfuscate_url_pw(clone_uri))
640
638
641 backend = get_backend(repo_type)
639 backend = get_backend(repo_type)
642
640
643 if repo_type == 'hg':
641 if repo_type == 'hg':
644 baseui = make_ui()
642 baseui = make_ui()
645 # patch and reset hooks section of UI config to not run any
643 # patch and reset hooks section of UI config to not run any
646 # hooks on creating remote repo
644 # hooks on creating remote repo
647 for k, v in baseui.configitems('hooks'):
645 for k, v in baseui.configitems('hooks'):
648 baseui.setconfig('hooks', k, None)
646 baseui.setconfig('hooks', k, None)
649
647
650 repo = backend(repo_path, create=True, src_url=clone_uri, baseui=baseui)
648 repo = backend(repo_path, create=True, src_url=clone_uri, baseui=baseui)
651 elif repo_type == 'git':
649 elif repo_type == 'git':
652 repo = backend(repo_path, create=True, src_url=clone_uri, bare=True)
650 repo = backend(repo_path, create=True, src_url=clone_uri, bare=True)
653 # add kallithea hook into this repo
651 # add kallithea hook into this repo
654 scm.ScmModel().install_git_hooks(repo)
652 scm.ScmModel().install_git_hooks(repo)
655 else:
653 else:
656 raise Exception('Not supported repo_type %s expected hg/git' % repo_type)
654 raise Exception('Not supported repo_type %s expected hg/git' % repo_type)
657
655
658 log.debug('Created repo %s with %s backend',
656 log.debug('Created repo %s with %s backend',
659 repo_name, repo_type)
657 repo_name, repo_type)
660 return repo
658 return repo
661
659
662 def _rename_filesystem_repo(self, old, new):
660 def _rename_filesystem_repo(self, old, new):
663 """
661 """
664 renames repository on filesystem
662 renames repository on filesystem
665
663
666 :param old: old name
664 :param old: old name
667 :param new: new name
665 :param new: new name
668 """
666 """
669 log.info('renaming repo from %s to %s', old, new)
667 log.info('renaming repo from %s to %s', old, new)
670
668
671 old_path = os.path.join(self.repos_path, old)
669 old_path = os.path.join(self.repos_path, old)
672 new_path = os.path.join(self.repos_path, new)
670 new_path = os.path.join(self.repos_path, new)
673 if os.path.isdir(new_path):
671 if os.path.isdir(new_path):
674 raise Exception(
672 raise Exception(
675 'Was trying to rename to already existing dir %s' % new_path
673 'Was trying to rename to already existing dir %s' % new_path
676 )
674 )
677 shutil.move(old_path, new_path)
675 shutil.move(old_path, new_path)
678
676
679 def _delete_filesystem_repo(self, repo):
677 def _delete_filesystem_repo(self, repo):
680 """
678 """
681 removes repo from filesystem, the removal is actually done by
679 removes repo from filesystem, the removal is actually done by
682 renaming dir to a 'rm__*' prefix which Kallithea will skip.
680 renaming dir to a 'rm__*' prefix which Kallithea will skip.
683 It can be undeleted later by reverting the rename.
681 It can be undeleted later by reverting the rename.
684
682
685 :param repo: repo object
683 :param repo: repo object
686 """
684 """
687 rm_path = os.path.join(self.repos_path, repo.repo_name)
685 rm_path = os.path.join(self.repos_path, repo.repo_name)
688 log.info("Removing %s", rm_path)
686 log.info("Removing %s", rm_path)
689
687
690 _now = datetime.now()
688 _now = datetime.now()
691 _ms = str(_now.microsecond).rjust(6, '0')
689 _ms = str(_now.microsecond).rjust(6, '0')
692 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
690 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
693 repo.just_name)
691 repo.just_name)
694 if repo.group:
692 if repo.group:
695 args = repo.group.full_path_splitted + [_d]
693 args = repo.group.full_path_splitted + [_d]
696 _d = os.path.join(*args)
694 _d = os.path.join(*args)
697 if os.path.exists(rm_path):
695 if os.path.exists(rm_path):
698 shutil.move(rm_path, os.path.join(self.repos_path, _d))
696 shutil.move(rm_path, os.path.join(self.repos_path, _d))
699 else:
697 else:
700 log.error("Can't find repo to delete in %r", rm_path)
698 log.error("Can't find repo to delete in %r", rm_path)
@@ -1,736 +1,733 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.scm
15 kallithea.model.scm
16 ~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~
17
17
18 Scm model for Kallithea
18 Scm model for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 9, 2010
22 :created_on: Apr 9, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import os
29 import os
30 import posixpath
30 import posixpath
31 import re
31 import re
32 import sys
32 import sys
33 import tempfile
33 import tempfile
34 import traceback
34 import traceback
35
35
36 import pkg_resources
36 import pkg_resources
37 from tg.i18n import ugettext as _
37 from tg.i18n import ugettext as _
38
38
39 import kallithea
39 import kallithea
40 from kallithea.lib import hooks
40 from kallithea.lib import hooks
41 from kallithea.lib.auth import HasPermissionAny, HasRepoGroupPermissionLevel, HasRepoPermissionLevel, HasUserGroupPermissionLevel
41 from kallithea.lib.auth import HasPermissionAny, HasRepoGroupPermissionLevel, HasRepoPermissionLevel, HasUserGroupPermissionLevel
42 from kallithea.lib.exceptions import IMCCommitError, NonRelativePathError
42 from kallithea.lib.exceptions import IMCCommitError, NonRelativePathError
43 from kallithea.lib.utils import get_filesystem_repos, make_ui
43 from kallithea.lib.utils import get_filesystem_repos, make_ui
44 from kallithea.lib.utils2 import safe_bytes, safe_str, set_hook_environment, umask
44 from kallithea.lib.utils2 import safe_bytes, safe_str, set_hook_environment, umask
45 from kallithea.lib.vcs import get_repo
45 from kallithea.lib.vcs import get_repo
46 from kallithea.lib.vcs.backends.base import EmptyChangeset
46 from kallithea.lib.vcs.backends.base import EmptyChangeset
47 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError
47 from kallithea.lib.vcs.exceptions import RepositoryError, VCSError
48 from kallithea.lib.vcs.nodes import FileNode
48 from kallithea.lib.vcs.nodes import FileNode
49 from kallithea.lib.vcs.utils.lazy import LazyProperty
49 from kallithea.lib.vcs.utils.lazy import LazyProperty
50 from kallithea.model import db, meta, userlog
50 from kallithea.model import db, meta, userlog
51
51
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class UserTemp(object):
56 class UserTemp(object):
57 def __init__(self, user_id):
57 def __init__(self, user_id):
58 self.user_id = user_id
58 self.user_id = user_id
59
59
60 def __repr__(self):
60 def __repr__(self):
61 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
61 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
62
62
63
63
64 class RepoTemp(object):
64 class RepoTemp(object):
65 def __init__(self, repo_id):
65 def __init__(self, repo_id):
66 self.repo_id = repo_id
66 self.repo_id = repo_id
67
67
68 def __repr__(self):
68 def __repr__(self):
69 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
69 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
70
70
71
71
72 class _PermCheckIterator(object):
72 class _PermCheckIterator(object):
73 def __init__(self, obj_list, obj_attr, perm_set, perm_checker, extra_kwargs=None):
73 def __init__(self, obj_list, obj_attr, perm_set, perm_checker, extra_kwargs=None):
74 """
74 """
75 Creates iterator from given list of objects, additionally
75 Creates iterator from given list of objects, additionally
76 checking permission for them from perm_set var
76 checking permission for them from perm_set var
77
77
78 :param obj_list: list of db objects
78 :param obj_list: list of db objects
79 :param obj_attr: attribute of object to pass into perm_checker
79 :param obj_attr: attribute of object to pass into perm_checker
80 :param perm_set: list of permissions to check
80 :param perm_set: list of permissions to check
81 :param perm_checker: callable to check permissions against
81 :param perm_checker: callable to check permissions against
82 """
82 """
83 self.obj_list = obj_list
83 self.obj_list = obj_list
84 self.obj_attr = obj_attr
84 self.obj_attr = obj_attr
85 self.perm_set = perm_set
85 self.perm_set = perm_set
86 self.perm_checker = perm_checker
86 self.perm_checker = perm_checker
87 self.extra_kwargs = extra_kwargs or {}
87 self.extra_kwargs = extra_kwargs or {}
88
88
89 def __len__(self):
89 def __len__(self):
90 return len(self.obj_list)
90 return len(self.obj_list)
91
91
92 def __repr__(self):
92 def __repr__(self):
93 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
93 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
94
94
95 def __iter__(self):
95 def __iter__(self):
96 for db_obj in self.obj_list:
96 for db_obj in self.obj_list:
97 # check permission at this level
97 # check permission at this level
98 name = getattr(db_obj, self.obj_attr, None)
98 name = getattr(db_obj, self.obj_attr, None)
99 if not self.perm_checker(*self.perm_set)(
99 if not self.perm_checker(*self.perm_set)(
100 name, self.__class__.__name__, **self.extra_kwargs):
100 name, self.__class__.__name__, **self.extra_kwargs):
101 continue
101 continue
102
102
103 yield db_obj
103 yield db_obj
104
104
105
105
106 class RepoList(_PermCheckIterator):
106 class RepoList(_PermCheckIterator):
107
107
108 def __init__(self, db_repo_list, perm_level, extra_kwargs=None):
108 def __init__(self, db_repo_list, perm_level, extra_kwargs=None):
109 super(RepoList, self).__init__(obj_list=db_repo_list,
109 super(RepoList, self).__init__(obj_list=db_repo_list,
110 obj_attr='repo_name', perm_set=[perm_level],
110 obj_attr='repo_name', perm_set=[perm_level],
111 perm_checker=HasRepoPermissionLevel,
111 perm_checker=HasRepoPermissionLevel,
112 extra_kwargs=extra_kwargs)
112 extra_kwargs=extra_kwargs)
113
113
114
114
115 class RepoGroupList(_PermCheckIterator):
115 class RepoGroupList(_PermCheckIterator):
116
116
117 def __init__(self, db_repo_group_list, perm_level, extra_kwargs=None):
117 def __init__(self, db_repo_group_list, perm_level, extra_kwargs=None):
118 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
118 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
119 obj_attr='group_name', perm_set=[perm_level],
119 obj_attr='group_name', perm_set=[perm_level],
120 perm_checker=HasRepoGroupPermissionLevel,
120 perm_checker=HasRepoGroupPermissionLevel,
121 extra_kwargs=extra_kwargs)
121 extra_kwargs=extra_kwargs)
122
122
123
123
124 class UserGroupList(_PermCheckIterator):
124 class UserGroupList(_PermCheckIterator):
125
125
126 def __init__(self, db_user_group_list, perm_level, extra_kwargs=None):
126 def __init__(self, db_user_group_list, perm_level, extra_kwargs=None):
127 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
127 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
128 obj_attr='users_group_name', perm_set=[perm_level],
128 obj_attr='users_group_name', perm_set=[perm_level],
129 perm_checker=HasUserGroupPermissionLevel,
129 perm_checker=HasUserGroupPermissionLevel,
130 extra_kwargs=extra_kwargs)
130 extra_kwargs=extra_kwargs)
131
131
132
132
133 class ScmModel(object):
133 class ScmModel(object):
134 """
134 """
135 Generic Scm Model
135 Generic Scm Model
136 """
136 """
137
137
138 def __get_repo(self, instance):
138 def __get_repo(self, instance):
139 cls = db.Repository
139 cls = db.Repository
140 if isinstance(instance, cls):
140 if isinstance(instance, cls):
141 return instance
141 return instance
142 elif isinstance(instance, int):
142 elif isinstance(instance, int):
143 return cls.get(instance)
143 return cls.get(instance)
144 elif isinstance(instance, str):
144 elif isinstance(instance, str):
145 if instance.isdigit():
145 if instance.isdigit():
146 return cls.get(int(instance))
146 return cls.get(int(instance))
147 return cls.get_by_repo_name(instance)
147 return cls.get_by_repo_name(instance)
148 raise Exception('given object must be int, basestr or Instance'
148 raise Exception('given object must be int, basestr or Instance'
149 ' of %s got %s' % (type(cls), type(instance)))
149 ' of %s got %s' % (type(cls), type(instance)))
150
150
151 @LazyProperty
151 @LazyProperty
152 def repos_path(self):
152 def repos_path(self):
153 """
153 """
154 Gets the repositories root path from database
154 Gets the repositories root path from database
155 """
155 """
156
156
157 q = db.Ui.query().filter(db.Ui.ui_key == '/').one()
157 q = db.Ui.query().filter(db.Ui.ui_key == '/').one()
158
158
159 return q.ui_value
159 return q.ui_value
160
160
161 def repo_scan(self, repos_path=None):
161 def repo_scan(self, repos_path=None):
162 """
162 """
163 Listing of repositories in given path. This path should not be a
163 Listing of repositories in given path. This path should not be a
164 repository itself. Return a dictionary of repository objects mapping to
164 repository itself. Return a dictionary of repository objects mapping to
165 vcs instances.
165 vcs instances.
166
166
167 :param repos_path: path to directory containing repositories
167 :param repos_path: path to directory containing repositories
168 """
168 """
169
169
170 if repos_path is None:
170 if repos_path is None:
171 repos_path = self.repos_path
171 repos_path = self.repos_path
172
172
173 log.info('scanning for repositories in %s', repos_path)
173 log.info('scanning for repositories in %s', repos_path)
174
174
175 baseui = make_ui()
175 baseui = make_ui()
176 repos = {}
176 repos = {}
177
177
178 for name, path in get_filesystem_repos(repos_path):
178 for name, path in get_filesystem_repos(repos_path):
179 # name need to be decomposed and put back together using the /
179 # name need to be decomposed and put back together using the /
180 # since this is internal storage separator for kallithea
180 # since this is internal storage separator for kallithea
181 name = db.Repository.normalize_repo_name(name)
181 name = db.Repository.normalize_repo_name(name)
182
182
183 try:
183 try:
184 if name in repos:
184 if name in repos:
185 raise RepositoryError('Duplicate repository name %s '
185 raise RepositoryError('Duplicate repository name %s '
186 'found in %s' % (name, path))
186 'found in %s' % (name, path))
187 else:
187 else:
188 repos[name] = get_repo(path[1], baseui=baseui)
188 repos[name] = get_repo(path[1], baseui=baseui)
189 except (OSError, VCSError):
189 except (OSError, VCSError):
190 continue
190 continue
191 log.debug('found %s paths with repositories', len(repos))
191 log.debug('found %s paths with repositories', len(repos))
192 return repos
192 return repos
193
193
194 def get_repos(self, repos):
194 def get_repos(self, repos):
195 """Return the repos the user has access to"""
195 """Return the repos the user has access to"""
196 return RepoList(repos, perm_level='read')
196 return RepoList(repos, perm_level='read')
197
197
198 def get_repo_groups(self, groups=None):
198 def get_repo_groups(self, groups=None):
199 """Return the repo groups the user has access to
199 """Return the repo groups the user has access to
200 If no groups are specified, use top level groups.
200 If no groups are specified, use top level groups.
201 """
201 """
202 if groups is None:
202 if groups is None:
203 groups = db.RepoGroup.query() \
203 groups = db.RepoGroup.query() \
204 .filter(db.RepoGroup.parent_group_id == None).all()
204 .filter(db.RepoGroup.parent_group_id == None).all()
205 return RepoGroupList(groups, perm_level='read')
205 return RepoGroupList(groups, perm_level='read')
206
206
207 def mark_for_invalidation(self, repo_name):
207 def mark_for_invalidation(self, repo_name):
208 """
208 """
209 Mark caches of this repo invalid in the database.
209 Mark caches of this repo invalid in the database.
210
210
211 :param repo_name: the repo for which caches should be marked invalid
211 :param repo_name: the repo for which caches should be marked invalid
212 """
212 """
213 log.debug("Marking %s as invalidated and update cache", repo_name)
213 log.debug("Marking %s as invalidated and update cache", repo_name)
214 repo = db.Repository.get_by_repo_name(repo_name)
214 repo = db.Repository.get_by_repo_name(repo_name)
215 if repo is not None:
215 if repo is not None:
216 repo.set_invalidate()
216 repo.set_invalidate()
217 repo.update_changeset_cache()
217 repo.update_changeset_cache()
218
218
219 def toggle_following_repo(self, follow_repo_id, user_id):
219 def toggle_following_repo(self, follow_repo_id, user_id):
220
220
221 f = db.UserFollowing.query() \
221 f = db.UserFollowing.query() \
222 .filter(db.UserFollowing.follows_repository_id == follow_repo_id) \
222 .filter(db.UserFollowing.follows_repository_id == follow_repo_id) \
223 .filter(db.UserFollowing.user_id == user_id).scalar()
223 .filter(db.UserFollowing.user_id == user_id).scalar()
224
224
225 if f is not None:
225 if f is not None:
226 try:
226 try:
227 meta.Session().delete(f)
227 meta.Session().delete(f)
228 userlog.action_logger(UserTemp(user_id),
228 userlog.action_logger(UserTemp(user_id),
229 'stopped_following_repo',
229 'stopped_following_repo',
230 RepoTemp(follow_repo_id))
230 RepoTemp(follow_repo_id))
231 return
231 return
232 except Exception:
232 except Exception:
233 log.error(traceback.format_exc())
233 log.error(traceback.format_exc())
234 raise
234 raise
235
235
236 try:
236 try:
237 f = db.UserFollowing()
237 f = db.UserFollowing()
238 f.user_id = user_id
238 f.user_id = user_id
239 f.follows_repository_id = follow_repo_id
239 f.follows_repository_id = follow_repo_id
240 meta.Session().add(f)
240 meta.Session().add(f)
241
241
242 userlog.action_logger(UserTemp(user_id),
242 userlog.action_logger(UserTemp(user_id),
243 'started_following_repo',
243 'started_following_repo',
244 RepoTemp(follow_repo_id))
244 RepoTemp(follow_repo_id))
245 except Exception:
245 except Exception:
246 log.error(traceback.format_exc())
246 log.error(traceback.format_exc())
247 raise
247 raise
248
248
249 def toggle_following_user(self, follow_user_id, user_id):
249 def toggle_following_user(self, follow_user_id, user_id):
250 f = db.UserFollowing.query() \
250 f = db.UserFollowing.query() \
251 .filter(db.UserFollowing.follows_user_id == follow_user_id) \
251 .filter(db.UserFollowing.follows_user_id == follow_user_id) \
252 .filter(db.UserFollowing.user_id == user_id).scalar()
252 .filter(db.UserFollowing.user_id == user_id).scalar()
253
253
254 if f is not None:
254 if f is not None:
255 try:
255 try:
256 meta.Session().delete(f)
256 meta.Session().delete(f)
257 return
257 return
258 except Exception:
258 except Exception:
259 log.error(traceback.format_exc())
259 log.error(traceback.format_exc())
260 raise
260 raise
261
261
262 try:
262 try:
263 f = db.UserFollowing()
263 f = db.UserFollowing()
264 f.user_id = user_id
264 f.user_id = user_id
265 f.follows_user_id = follow_user_id
265 f.follows_user_id = follow_user_id
266 meta.Session().add(f)
266 meta.Session().add(f)
267 except Exception:
267 except Exception:
268 log.error(traceback.format_exc())
268 log.error(traceback.format_exc())
269 raise
269 raise
270
270
271 def is_following_repo(self, repo_name, user_id):
271 def is_following_repo(self, repo_name, user_id):
272 r = db.Repository.query() \
272 r = db.Repository.query() \
273 .filter(db.Repository.repo_name == repo_name).scalar()
273 .filter(db.Repository.repo_name == repo_name).scalar()
274
274
275 f = db.UserFollowing.query() \
275 f = db.UserFollowing.query() \
276 .filter(db.UserFollowing.follows_repository == r) \
276 .filter(db.UserFollowing.follows_repository == r) \
277 .filter(db.UserFollowing.user_id == user_id).scalar()
277 .filter(db.UserFollowing.user_id == user_id).scalar()
278
278
279 return f is not None
279 return f is not None
280
280
281 def is_following_user(self, username, user_id):
281 def is_following_user(self, username, user_id):
282 u = db.User.get_by_username(username)
282 u = db.User.get_by_username(username)
283
283
284 f = db.UserFollowing.query() \
284 f = db.UserFollowing.query() \
285 .filter(db.UserFollowing.follows_user == u) \
285 .filter(db.UserFollowing.follows_user == u) \
286 .filter(db.UserFollowing.user_id == user_id).scalar()
286 .filter(db.UserFollowing.user_id == user_id).scalar()
287
287
288 return f is not None
288 return f is not None
289
289
290 def get_followers(self, repo):
290 def get_followers(self, repo):
291 repo = db.Repository.guess_instance(repo)
291 repo = db.Repository.guess_instance(repo)
292
292
293 return db.UserFollowing.query() \
293 return db.UserFollowing.query() \
294 .filter(db.UserFollowing.follows_repository == repo).count()
294 .filter(db.UserFollowing.follows_repository == repo).count()
295
295
296 def get_forks(self, repo):
296 def get_forks(self, repo):
297 repo = db.Repository.guess_instance(repo)
297 repo = db.Repository.guess_instance(repo)
298 return db.Repository.query() \
298 return db.Repository.query() \
299 .filter(db.Repository.fork == repo).count()
299 .filter(db.Repository.fork == repo).count()
300
300
301 def get_pull_requests(self, repo):
301 def get_pull_requests(self, repo):
302 repo = db.Repository.guess_instance(repo)
302 repo = db.Repository.guess_instance(repo)
303 return db.PullRequest.query() \
303 return db.PullRequest.query() \
304 .filter(db.PullRequest.other_repo == repo) \
304 .filter(db.PullRequest.other_repo == repo) \
305 .filter(db.PullRequest.status != db.PullRequest.STATUS_CLOSED).count()
305 .filter(db.PullRequest.status != db.PullRequest.STATUS_CLOSED).count()
306
306
307 def mark_as_fork(self, repo, fork, user):
307 def mark_as_fork(self, repo, fork, user):
308 repo = self.__get_repo(repo)
308 repo = self.__get_repo(repo)
309 fork = self.__get_repo(fork)
309 fork = self.__get_repo(fork)
310 if fork and repo.repo_id == fork.repo_id:
310 if fork and repo.repo_id == fork.repo_id:
311 raise Exception("Cannot set repository as fork of itself")
311 raise Exception("Cannot set repository as fork of itself")
312
312
313 if fork and repo.repo_type != fork.repo_type:
313 if fork and repo.repo_type != fork.repo_type:
314 raise RepositoryError("Cannot set repository as fork of repository with other type")
314 raise RepositoryError("Cannot set repository as fork of repository with other type")
315
315
316 repo.fork = fork
316 repo.fork = fork
317 return repo
317 return repo
318
318
319 def _handle_push(self, repo, username, ip_addr, action, repo_name, revisions):
319 def _handle_push(self, repo, username, ip_addr, action, repo_name, revisions):
320 """
320 """
321 Handle that the repository has changed.
321 Handle that the repository has changed.
322 Adds an action log entry with the new revisions, and the head revision
322 Adds an action log entry with the new revisions, and the head revision
323 cache and in-memory caches are invalidated/updated.
323 cache and in-memory caches are invalidated/updated.
324
324
325 :param username: username who pushes
325 :param username: username who pushes
326 :param action: push/push_local/push_remote
326 :param action: push/push_local/push_remote
327 :param repo_name: name of repo
327 :param repo_name: name of repo
328 :param revisions: list of revisions that we pushed
328 :param revisions: list of revisions that we pushed
329 """
329 """
330 set_hook_environment(username, ip_addr, repo_name, repo_alias=repo.alias, action=action)
330 set_hook_environment(username, ip_addr, repo_name, repo_alias=repo.alias, action=action)
331 hooks.process_pushed_raw_ids(revisions) # also calls mark_for_invalidation
331 hooks.process_pushed_raw_ids(revisions) # also calls mark_for_invalidation
332
332
333 def pull_changes(self, repo, username, ip_addr, clone_uri=None):
333 def pull_changes(self, repo, username, ip_addr, clone_uri=None):
334 """
334 """
335 Pull from "clone URL" or fork origin.
335 Pull from "clone URL" or fork origin.
336 """
336 """
337 dbrepo = self.__get_repo(repo)
337 dbrepo = self.__get_repo(repo)
338 if clone_uri is None:
338 if clone_uri is None:
339 clone_uri = dbrepo.clone_uri or dbrepo.fork and dbrepo.fork.repo_full_path
339 clone_uri = dbrepo.clone_uri or dbrepo.fork and dbrepo.fork.repo_full_path
340 if not clone_uri:
340 if not clone_uri:
341 raise Exception("This repository doesn't have a clone uri")
341 raise Exception("This repository doesn't have a clone uri")
342
342
343 repo = dbrepo.scm_instance
343 repo = dbrepo.scm_instance
344 repo_name = dbrepo.repo_name
344 repo_name = dbrepo.repo_name
345 try:
345 try:
346 if repo.alias == 'git':
346 if repo.alias == 'git':
347 repo.fetch(clone_uri)
347 repo.fetch(clone_uri)
348 # git doesn't really have something like post-fetch action
348 # git doesn't really have something like post-fetch action
349 # we fake that now.
349 # we fake that now.
350 # TODO: extract fetched revisions ... somehow ...
350 # TODO: extract fetched revisions ... somehow ...
351 self._handle_push(repo,
351 self._handle_push(repo,
352 username=username,
352 username=username,
353 ip_addr=ip_addr,
353 ip_addr=ip_addr,
354 action='push_remote',
354 action='push_remote',
355 repo_name=repo_name,
355 repo_name=repo_name,
356 revisions=[])
356 revisions=[])
357 else:
357 else:
358 set_hook_environment(username, ip_addr, dbrepo.repo_name,
358 set_hook_environment(username, ip_addr, dbrepo.repo_name,
359 repo.alias, action='push_remote')
359 repo.alias, action='push_remote')
360 repo.pull(clone_uri)
360 repo.pull(clone_uri)
361 except Exception:
361 except Exception:
362 log.error(traceback.format_exc())
362 log.error(traceback.format_exc())
363 raise
363 raise
364
364
365 def commit_change(self, repo, repo_name, cs, user, ip_addr, author, message,
365 def commit_change(self, repo, repo_name, cs, user, ip_addr, author, message,
366 content, f_path):
366 content, f_path):
367 """
367 """
368 Commit a change to a single file
368 Commit a change to a single file
369
369
370 :param repo: a db_repo.scm_instance
370 :param repo: a db_repo.scm_instance
371 """
371 """
372 user = db.User.guess_instance(user)
372 user = db.User.guess_instance(user)
373 imc = repo.in_memory_changeset
373 imc = repo.in_memory_changeset
374 imc.change(FileNode(f_path, content, mode=cs.get_file_mode(f_path)))
374 imc.change(FileNode(f_path, content, mode=cs.get_file_mode(f_path)))
375 try:
375 try:
376 tip = imc.commit(message=message, author=author,
376 tip = imc.commit(message=message, author=author,
377 parents=[cs], branch=cs.branch)
377 parents=[cs], branch=cs.branch)
378 except Exception as e:
378 except Exception as e:
379 log.error(traceback.format_exc())
379 log.error(traceback.format_exc())
380 # clear caches - we also want a fresh object if commit fails
380 # clear caches - we also want a fresh object if commit fails
381 self.mark_for_invalidation(repo_name)
381 self.mark_for_invalidation(repo_name)
382 raise IMCCommitError(str(e))
382 raise IMCCommitError(str(e))
383 self._handle_push(repo,
383 self._handle_push(repo,
384 username=user.username,
384 username=user.username,
385 ip_addr=ip_addr,
385 ip_addr=ip_addr,
386 action='push_local',
386 action='push_local',
387 repo_name=repo_name,
387 repo_name=repo_name,
388 revisions=[tip.raw_id])
388 revisions=[tip.raw_id])
389 return tip
389 return tip
390
390
391 def _sanitize_path(self, f_path):
391 def _sanitize_path(self, f_path):
392 if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
392 if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
393 raise NonRelativePathError('%s is not an relative path' % f_path)
393 raise NonRelativePathError('%s is not an relative path' % f_path)
394 if f_path:
394 if f_path:
395 f_path = posixpath.normpath(f_path)
395 f_path = posixpath.normpath(f_path)
396 return f_path
396 return f_path
397
397
398 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
398 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
399 """
399 """
400 Recursively walk root dir and return a set of all paths found.
400 Recursively walk root dir and return a set of all paths found.
401
401
402 :param repo_name: name of repository
402 :param repo_name: name of repository
403 :param revision: revision for which to list nodes
403 :param revision: revision for which to list nodes
404 :param root_path: root path to list
404 :param root_path: root path to list
405 :param flat: return as a list, if False returns a dict with description
405 :param flat: return as a list, if False returns a dict with description
406
406
407 """
407 """
408 _files = list()
408 _files = list()
409 _dirs = list()
409 _dirs = list()
410 try:
410 try:
411 _repo = self.__get_repo(repo_name)
411 _repo = self.__get_repo(repo_name)
412 changeset = _repo.scm_instance.get_changeset(revision)
412 changeset = _repo.scm_instance.get_changeset(revision)
413 root_path = root_path.lstrip('/')
413 root_path = root_path.lstrip('/')
414 for topnode, dirs, files in changeset.walk(root_path):
414 for topnode, dirs, files in changeset.walk(root_path):
415 for f in files:
415 for f in files:
416 _files.append(f.path if flat else {"name": f.path,
416 _files.append(f.path if flat else {"name": f.path,
417 "type": "file"})
417 "type": "file"})
418 for d in dirs:
418 for d in dirs:
419 _dirs.append(d.path if flat else {"name": d.path,
419 _dirs.append(d.path if flat else {"name": d.path,
420 "type": "dir"})
420 "type": "dir"})
421 except RepositoryError:
421 except RepositoryError:
422 log.debug(traceback.format_exc())
422 log.debug(traceback.format_exc())
423 raise
423 raise
424
424
425 return _dirs, _files
425 return _dirs, _files
426
426
427 def create_nodes(self, user, ip_addr, repo, message, nodes, parent_cs=None,
427 def create_nodes(self, user, ip_addr, repo, message, nodes, parent_cs=None,
428 author=None, trigger_push_hook=True):
428 author=None, trigger_push_hook=True):
429 """
429 """
430 Commits specified nodes to repo.
430 Commits specified nodes to repo.
431
431
432 :param user: Kallithea User object or user_id, the committer
432 :param user: Kallithea User object or user_id, the committer
433 :param repo: Kallithea Repository object
433 :param repo: Kallithea Repository object
434 :param message: commit message
434 :param message: commit message
435 :param nodes: mapping {filename:{'content':content},...}
435 :param nodes: mapping {filename:{'content':content},...}
436 :param parent_cs: parent changeset, can be empty than it's initial commit
436 :param parent_cs: parent changeset, can be empty than it's initial commit
437 :param author: author of commit, cna be different that committer only for git
437 :param author: author of commit, cna be different that committer only for git
438 :param trigger_push_hook: trigger push hooks
438 :param trigger_push_hook: trigger push hooks
439
439
440 :returns: new committed changeset
440 :returns: new committed changeset
441 """
441 """
442
442
443 user = db.User.guess_instance(user)
443 user = db.User.guess_instance(user)
444 scm_instance = repo.scm_instance_no_cache()
444 scm_instance = repo.scm_instance_no_cache()
445
445
446 processed_nodes = []
446 processed_nodes = []
447 for f_path in nodes:
447 for f_path in nodes:
448 content = nodes[f_path]['content']
448 content = nodes[f_path]['content']
449 f_path = self._sanitize_path(f_path)
449 f_path = self._sanitize_path(f_path)
450 if not isinstance(content, str) and not isinstance(content, bytes):
450 if not isinstance(content, str) and not isinstance(content, bytes):
451 content = content.read()
451 content = content.read()
452 processed_nodes.append((f_path, content))
452 processed_nodes.append((f_path, content))
453
453
454 message = message
455 committer = user.full_contact
454 committer = user.full_contact
456 if not author:
455 if not author:
457 author = committer
456 author = committer
458
457
459 if not parent_cs:
458 if not parent_cs:
460 parent_cs = EmptyChangeset(alias=scm_instance.alias)
459 parent_cs = EmptyChangeset(alias=scm_instance.alias)
461
460
462 if isinstance(parent_cs, EmptyChangeset):
461 if isinstance(parent_cs, EmptyChangeset):
463 # EmptyChangeset means we we're editing empty repository
462 # EmptyChangeset means we we're editing empty repository
464 parents = None
463 parents = None
465 else:
464 else:
466 parents = [parent_cs]
465 parents = [parent_cs]
467 # add multiple nodes
466 # add multiple nodes
468 imc = scm_instance.in_memory_changeset
467 imc = scm_instance.in_memory_changeset
469 for path, content in processed_nodes:
468 for path, content in processed_nodes:
470 imc.add(FileNode(path, content=content))
469 imc.add(FileNode(path, content=content))
471
470
472 tip = imc.commit(message=message,
471 tip = imc.commit(message=message,
473 author=author,
472 author=author,
474 parents=parents,
473 parents=parents,
475 branch=parent_cs.branch)
474 branch=parent_cs.branch)
476
475
477 if trigger_push_hook:
476 if trigger_push_hook:
478 self._handle_push(scm_instance,
477 self._handle_push(scm_instance,
479 username=user.username,
478 username=user.username,
480 ip_addr=ip_addr,
479 ip_addr=ip_addr,
481 action='push_local',
480 action='push_local',
482 repo_name=repo.repo_name,
481 repo_name=repo.repo_name,
483 revisions=[tip.raw_id])
482 revisions=[tip.raw_id])
484 else:
483 else:
485 self.mark_for_invalidation(repo.repo_name)
484 self.mark_for_invalidation(repo.repo_name)
486 return tip
485 return tip
487
486
488 def update_nodes(self, user, ip_addr, repo, message, nodes, parent_cs=None,
487 def update_nodes(self, user, ip_addr, repo, message, nodes, parent_cs=None,
489 author=None, trigger_push_hook=True):
488 author=None, trigger_push_hook=True):
490 """
489 """
491 Commits specified nodes to repo. Again.
490 Commits specified nodes to repo. Again.
492 """
491 """
493 user = db.User.guess_instance(user)
492 user = db.User.guess_instance(user)
494 scm_instance = repo.scm_instance_no_cache()
493 scm_instance = repo.scm_instance_no_cache()
495
494
496 message = message
497 committer = user.full_contact
495 committer = user.full_contact
498 if not author:
496 if not author:
499 author = committer
497 author = committer
500
498
501 if not parent_cs:
499 if not parent_cs:
502 parent_cs = EmptyChangeset(alias=scm_instance.alias)
500 parent_cs = EmptyChangeset(alias=scm_instance.alias)
503
501
504 if isinstance(parent_cs, EmptyChangeset):
502 if isinstance(parent_cs, EmptyChangeset):
505 # EmptyChangeset means we we're editing empty repository
503 # EmptyChangeset means we we're editing empty repository
506 parents = None
504 parents = None
507 else:
505 else:
508 parents = [parent_cs]
506 parents = [parent_cs]
509
507
510 # add multiple nodes
508 # add multiple nodes
511 imc = scm_instance.in_memory_changeset
509 imc = scm_instance.in_memory_changeset
512 for _filename, data in nodes.items():
510 for _filename, data in nodes.items():
513 # new filename, can be renamed from the old one
511 # new filename, can be renamed from the old one
514 filename = self._sanitize_path(data['filename'])
512 filename = self._sanitize_path(data['filename'])
515 old_filename = self._sanitize_path(_filename)
513 old_filename = self._sanitize_path(_filename)
516 content = data['content']
514 content = data['content']
517
515
518 filenode = FileNode(old_filename, content=content)
516 filenode = FileNode(old_filename, content=content)
519 op = data['op']
517 op = data['op']
520 if op == 'add':
518 if op == 'add':
521 imc.add(filenode)
519 imc.add(filenode)
522 elif op == 'del':
520 elif op == 'del':
523 imc.remove(filenode)
521 imc.remove(filenode)
524 elif op == 'mod':
522 elif op == 'mod':
525 if filename != old_filename:
523 if filename != old_filename:
526 # TODO: handle renames, needs vcs lib changes
524 # TODO: handle renames, needs vcs lib changes
527 imc.remove(filenode)
525 imc.remove(filenode)
528 imc.add(FileNode(filename, content=content))
526 imc.add(FileNode(filename, content=content))
529 else:
527 else:
530 imc.change(filenode)
528 imc.change(filenode)
531
529
532 # commit changes
530 # commit changes
533 tip = imc.commit(message=message,
531 tip = imc.commit(message=message,
534 author=author,
532 author=author,
535 parents=parents,
533 parents=parents,
536 branch=parent_cs.branch)
534 branch=parent_cs.branch)
537
535
538 if trigger_push_hook:
536 if trigger_push_hook:
539 self._handle_push(scm_instance,
537 self._handle_push(scm_instance,
540 username=user.username,
538 username=user.username,
541 ip_addr=ip_addr,
539 ip_addr=ip_addr,
542 action='push_local',
540 action='push_local',
543 repo_name=repo.repo_name,
541 repo_name=repo.repo_name,
544 revisions=[tip.raw_id])
542 revisions=[tip.raw_id])
545 else:
543 else:
546 self.mark_for_invalidation(repo.repo_name)
544 self.mark_for_invalidation(repo.repo_name)
547
545
548 def delete_nodes(self, user, ip_addr, repo, message, nodes, parent_cs=None,
546 def delete_nodes(self, user, ip_addr, repo, message, nodes, parent_cs=None,
549 author=None, trigger_push_hook=True):
547 author=None, trigger_push_hook=True):
550 """
548 """
551 Deletes specified nodes from repo.
549 Deletes specified nodes from repo.
552
550
553 :param user: Kallithea User object or user_id, the committer
551 :param user: Kallithea User object or user_id, the committer
554 :param repo: Kallithea Repository object
552 :param repo: Kallithea Repository object
555 :param message: commit message
553 :param message: commit message
556 :param nodes: mapping {filename:{'content':content},...}
554 :param nodes: mapping {filename:{'content':content},...}
557 :param parent_cs: parent changeset, can be empty than it's initial commit
555 :param parent_cs: parent changeset, can be empty than it's initial commit
558 :param author: author of commit, cna be different that committer only for git
556 :param author: author of commit, cna be different that committer only for git
559 :param trigger_push_hook: trigger push hooks
557 :param trigger_push_hook: trigger push hooks
560
558
561 :returns: new committed changeset after deletion
559 :returns: new committed changeset after deletion
562 """
560 """
563
561
564 user = db.User.guess_instance(user)
562 user = db.User.guess_instance(user)
565 scm_instance = repo.scm_instance_no_cache()
563 scm_instance = repo.scm_instance_no_cache()
566
564
567 processed_nodes = []
565 processed_nodes = []
568 for f_path in nodes:
566 for f_path in nodes:
569 f_path = self._sanitize_path(f_path)
567 f_path = self._sanitize_path(f_path)
570 # content can be empty but for compatibility it allows same dicts
568 # content can be empty but for compatibility it allows same dicts
571 # structure as add_nodes
569 # structure as add_nodes
572 content = nodes[f_path].get('content')
570 content = nodes[f_path].get('content')
573 processed_nodes.append((f_path, content))
571 processed_nodes.append((f_path, content))
574
572
575 message = message
576 committer = user.full_contact
573 committer = user.full_contact
577 if not author:
574 if not author:
578 author = committer
575 author = committer
579
576
580 if not parent_cs:
577 if not parent_cs:
581 parent_cs = EmptyChangeset(alias=scm_instance.alias)
578 parent_cs = EmptyChangeset(alias=scm_instance.alias)
582
579
583 if isinstance(parent_cs, EmptyChangeset):
580 if isinstance(parent_cs, EmptyChangeset):
584 # EmptyChangeset means we we're editing empty repository
581 # EmptyChangeset means we we're editing empty repository
585 parents = None
582 parents = None
586 else:
583 else:
587 parents = [parent_cs]
584 parents = [parent_cs]
588 # add multiple nodes
585 # add multiple nodes
589 imc = scm_instance.in_memory_changeset
586 imc = scm_instance.in_memory_changeset
590 for path, content in processed_nodes:
587 for path, content in processed_nodes:
591 imc.remove(FileNode(path, content=content))
588 imc.remove(FileNode(path, content=content))
592
589
593 tip = imc.commit(message=message,
590 tip = imc.commit(message=message,
594 author=author,
591 author=author,
595 parents=parents,
592 parents=parents,
596 branch=parent_cs.branch)
593 branch=parent_cs.branch)
597
594
598 if trigger_push_hook:
595 if trigger_push_hook:
599 self._handle_push(scm_instance,
596 self._handle_push(scm_instance,
600 username=user.username,
597 username=user.username,
601 ip_addr=ip_addr,
598 ip_addr=ip_addr,
602 action='push_local',
599 action='push_local',
603 repo_name=repo.repo_name,
600 repo_name=repo.repo_name,
604 revisions=[tip.raw_id])
601 revisions=[tip.raw_id])
605 else:
602 else:
606 self.mark_for_invalidation(repo.repo_name)
603 self.mark_for_invalidation(repo.repo_name)
607 return tip
604 return tip
608
605
609 def get_unread_journal(self):
606 def get_unread_journal(self):
610 return db.UserLog.query().count()
607 return db.UserLog.query().count()
611
608
612 def get_repo_landing_revs(self, repo=None):
609 def get_repo_landing_revs(self, repo=None):
613 """
610 """
614 Generates select option with tags branches and bookmarks (for hg only)
611 Generates select option with tags branches and bookmarks (for hg only)
615 grouped by type
612 grouped by type
616
613
617 :param repo:
614 :param repo:
618 """
615 """
619
616
620 hist_l = []
617 hist_l = []
621 choices = []
618 choices = []
622 hist_l.append(('rev:tip', _('latest tip')))
619 hist_l.append(('rev:tip', _('latest tip')))
623 choices.append('rev:tip')
620 choices.append('rev:tip')
624 if repo is None:
621 if repo is None:
625 return choices, hist_l
622 return choices, hist_l
626
623
627 repo = self.__get_repo(repo)
624 repo = self.__get_repo(repo)
628 repo = repo.scm_instance
625 repo = repo.scm_instance
629
626
630 branches_group = ([('branch:%s' % k, k) for k, v in
627 branches_group = ([('branch:%s' % k, k) for k, v in
631 repo.branches.items()], _("Branches"))
628 repo.branches.items()], _("Branches"))
632 hist_l.append(branches_group)
629 hist_l.append(branches_group)
633 choices.extend([x[0] for x in branches_group[0]])
630 choices.extend([x[0] for x in branches_group[0]])
634
631
635 if repo.alias == 'hg':
632 if repo.alias == 'hg':
636 bookmarks_group = ([('book:%s' % k, k) for k, v in
633 bookmarks_group = ([('book:%s' % k, k) for k, v in
637 repo.bookmarks.items()], _("Bookmarks"))
634 repo.bookmarks.items()], _("Bookmarks"))
638 hist_l.append(bookmarks_group)
635 hist_l.append(bookmarks_group)
639 choices.extend([x[0] for x in bookmarks_group[0]])
636 choices.extend([x[0] for x in bookmarks_group[0]])
640
637
641 tags_group = ([('tag:%s' % k, k) for k, v in
638 tags_group = ([('tag:%s' % k, k) for k, v in
642 repo.tags.items()], _("Tags"))
639 repo.tags.items()], _("Tags"))
643 hist_l.append(tags_group)
640 hist_l.append(tags_group)
644 choices.extend([x[0] for x in tags_group[0]])
641 choices.extend([x[0] for x in tags_group[0]])
645
642
646 return choices, hist_l
643 return choices, hist_l
647
644
648 def _get_git_hook_interpreter(self):
645 def _get_git_hook_interpreter(self):
649 """Return a suitable interpreter for Git hooks.
646 """Return a suitable interpreter for Git hooks.
650
647
651 Return a suitable string to be written in the POSIX #! shebang line for
648 Return a suitable string to be written in the POSIX #! shebang line for
652 Git hook scripts so they invoke Kallithea code with the right Python
649 Git hook scripts so they invoke Kallithea code with the right Python
653 interpreter and in the right environment.
650 interpreter and in the right environment.
654 """
651 """
655 # Note: sys.executable might not point at a usable Python interpreter. For
652 # Note: sys.executable might not point at a usable Python interpreter. For
656 # example, when using uwsgi, it will point at the uwsgi program itself.
653 # example, when using uwsgi, it will point at the uwsgi program itself.
657 # FIXME This may not work on Windows and may need a shell wrapper script.
654 # FIXME This may not work on Windows and may need a shell wrapper script.
658 return (kallithea.CONFIG.get('git_hook_interpreter')
655 return (kallithea.CONFIG.get('git_hook_interpreter')
659 or sys.executable
656 or sys.executable
660 or '/usr/bin/env python3')
657 or '/usr/bin/env python3')
661
658
662 def install_git_hooks(self, repo, force=False):
659 def install_git_hooks(self, repo, force=False):
663 """
660 """
664 Creates a kallithea hook inside a git repository
661 Creates a kallithea hook inside a git repository
665
662
666 :param repo: Instance of VCS repo
663 :param repo: Instance of VCS repo
667 :param force: Overwrite existing non-Kallithea hooks
664 :param force: Overwrite existing non-Kallithea hooks
668 """
665 """
669
666
670 hooks_path = os.path.join(repo.path, 'hooks')
667 hooks_path = os.path.join(repo.path, 'hooks')
671 if not repo.bare:
668 if not repo.bare:
672 hooks_path = os.path.join(repo.path, '.git', 'hooks')
669 hooks_path = os.path.join(repo.path, '.git', 'hooks')
673 if not os.path.isdir(hooks_path):
670 if not os.path.isdir(hooks_path):
674 os.makedirs(hooks_path)
671 os.makedirs(hooks_path)
675
672
676 tmpl_post = b"#!%s\n" % safe_bytes(self._get_git_hook_interpreter())
673 tmpl_post = b"#!%s\n" % safe_bytes(self._get_git_hook_interpreter())
677 tmpl_post += pkg_resources.resource_string(
674 tmpl_post += pkg_resources.resource_string(
678 'kallithea', os.path.join('templates', 'py', 'git_post_receive_hook.py')
675 'kallithea', os.path.join('templates', 'py', 'git_post_receive_hook.py')
679 )
676 )
680 tmpl_pre = b"#!%s\n" % safe_bytes(self._get_git_hook_interpreter())
677 tmpl_pre = b"#!%s\n" % safe_bytes(self._get_git_hook_interpreter())
681 tmpl_pre += pkg_resources.resource_string(
678 tmpl_pre += pkg_resources.resource_string(
682 'kallithea', os.path.join('templates', 'py', 'git_pre_receive_hook.py')
679 'kallithea', os.path.join('templates', 'py', 'git_pre_receive_hook.py')
683 )
680 )
684
681
685 for h_type, tmpl in [('pre-receive', tmpl_pre), ('post-receive', tmpl_post)]:
682 for h_type, tmpl in [('pre-receive', tmpl_pre), ('post-receive', tmpl_post)]:
686 hook_file = os.path.join(hooks_path, h_type)
683 hook_file = os.path.join(hooks_path, h_type)
687 other_hook = False
684 other_hook = False
688 log.debug('Installing git hook in repo %s', repo)
685 log.debug('Installing git hook in repo %s', repo)
689 if os.path.islink(hook_file):
686 if os.path.islink(hook_file):
690 log.debug("Found symlink hook at %s", hook_file)
687 log.debug("Found symlink hook at %s", hook_file)
691 other_hook = True
688 other_hook = True
692 elif os.path.isfile(hook_file):
689 elif os.path.isfile(hook_file):
693 log.debug('hook exists, checking if it is from kallithea')
690 log.debug('hook exists, checking if it is from kallithea')
694 with open(hook_file, 'rb') as f:
691 with open(hook_file, 'rb') as f:
695 data = f.read()
692 data = f.read()
696 matches = re.search(br'^KALLITHEA_HOOK_VER\s*=\s*(.*)$', data, flags=re.MULTILINE)
693 matches = re.search(br'^KALLITHEA_HOOK_VER\s*=\s*(.*)$', data, flags=re.MULTILINE)
697 if matches:
694 if matches:
698 ver = safe_str(matches.group(1))
695 ver = safe_str(matches.group(1))
699 log.debug('Found Kallithea hook - it has KALLITHEA_HOOK_VER %s', ver)
696 log.debug('Found Kallithea hook - it has KALLITHEA_HOOK_VER %s', ver)
700 else:
697 else:
701 log.debug('Found non-Kallithea hook at %s', hook_file)
698 log.debug('Found non-Kallithea hook at %s', hook_file)
702 other_hook = True
699 other_hook = True
703 elif os.path.exists(hook_file):
700 elif os.path.exists(hook_file):
704 log.debug("Found hook that isn't a regular file at %s", hook_file)
701 log.debug("Found hook that isn't a regular file at %s", hook_file)
705 other_hook = True
702 other_hook = True
706 if other_hook and not force:
703 if other_hook and not force:
707 log.warning('skipping overwriting hook file %s', hook_file)
704 log.warning('skipping overwriting hook file %s', hook_file)
708 else:
705 else:
709 log.debug('writing %s hook file !', h_type)
706 log.debug('writing %s hook file !', h_type)
710 try:
707 try:
711 fh, fn = tempfile.mkstemp(prefix=hook_file + '.tmp.')
708 fh, fn = tempfile.mkstemp(prefix=hook_file + '.tmp.')
712 os.write(fh, tmpl.replace(b'_TMPL_', safe_bytes(kallithea.__version__)))
709 os.write(fh, tmpl.replace(b'_TMPL_', safe_bytes(kallithea.__version__)))
713 os.close(fh)
710 os.close(fh)
714 os.chmod(fn, 0o777 & ~umask)
711 os.chmod(fn, 0o777 & ~umask)
715 os.rename(fn, hook_file)
712 os.rename(fn, hook_file)
716 except (OSError, IOError) as e:
713 except (OSError, IOError) as e:
717 log.error('error writing hook %s: %s', hook_file, e)
714 log.error('error writing hook %s: %s', hook_file, e)
718
715
719
716
720 def AvailableRepoGroupChoices(repo_group_perm_level, extras=()):
717 def AvailableRepoGroupChoices(repo_group_perm_level, extras=()):
721 """Return group_id,string tuples with choices for all the repo groups where
718 """Return group_id,string tuples with choices for all the repo groups where
722 the user has the necessary permissions.
719 the user has the necessary permissions.
723
720
724 Top level is -1.
721 Top level is -1.
725 """
722 """
726 groups = db.RepoGroup.query().all()
723 groups = db.RepoGroup.query().all()
727 if HasPermissionAny('hg.admin')('available repo groups'):
724 if HasPermissionAny('hg.admin')('available repo groups'):
728 groups.append(None)
725 groups.append(None)
729 else:
726 else:
730 groups = list(RepoGroupList(groups, perm_level=repo_group_perm_level))
727 groups = list(RepoGroupList(groups, perm_level=repo_group_perm_level))
731 if HasPermissionAny('hg.create.repository')('available repo groups'):
728 if HasPermissionAny('hg.create.repository')('available repo groups'):
732 groups.append(None)
729 groups.append(None)
733 for extra in extras:
730 for extra in extras:
734 if not any(rg == extra for rg in groups):
731 if not any(rg == extra for rg in groups):
735 groups.append(extra)
732 groups.append(extra)
736 return db.RepoGroup.groups_choices(groups=groups)
733 return db.RepoGroup.groups_choices(groups=groups)
@@ -1,191 +1,190 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
6 # (at your option) any later version.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU General Public License
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 """
15 """
16 kallithea.tests.scripts.manual_test_crawler
16 kallithea.tests.scripts.manual_test_crawler
17 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
18
18
19 Test for crawling a project for memory usage
19 Test for crawling a project for memory usage
20 This should be runned just as regular script together
20 This should be runned just as regular script together
21 with a watch script that will show memory usage.
21 with a watch script that will show memory usage.
22
22
23 watch -n1 ./kallithea/tests/mem_watch
23 watch -n1 ./kallithea/tests/mem_watch
24
24
25 This file was forked by the Kallithea project in July 2014.
25 This file was forked by the Kallithea project in July 2014.
26 Original author and date, and relevant copyright and licensing information is below:
26 Original author and date, and relevant copyright and licensing information is below:
27 :created_on: Apr 21, 2010
27 :created_on: Apr 21, 2010
28 :author: marcink
28 :author: marcink
29 :copyright: (c) 2013 RhodeCode GmbH, and others.
29 :copyright: (c) 2013 RhodeCode GmbH, and others.
30 :license: GPLv3, see LICENSE.md for more details.
30 :license: GPLv3, see LICENSE.md for more details.
31 """
31 """
32
32
33 import http.cookiejar
33 import http.cookiejar
34 import os
34 import os
35 import sys
35 import sys
36 import tempfile
36 import tempfile
37 import time
37 import time
38 import urllib.parse
38 import urllib.parse
39 import urllib.request
39 import urllib.request
40 from os.path import dirname
40 from os.path import dirname
41
41
42 from kallithea.lib import vcs
42 from kallithea.lib import vcs
43 from kallithea.lib.compat import OrderedSet
43 from kallithea.lib.compat import OrderedSet
44 from kallithea.lib.vcs.exceptions import RepositoryError
44 from kallithea.lib.vcs.exceptions import RepositoryError
45
45
46
46
47 __here__ = os.path.abspath(__file__)
47 __here__ = os.path.abspath(__file__)
48 __root__ = dirname(dirname(dirname(__here__)))
48 __root__ = dirname(dirname(dirname(__here__)))
49 sys.path.append(__root__)
49 sys.path.append(__root__)
50
50
51
51
52 PASES = 3
52 PASES = 3
53 HOST = 'http://127.0.0.1'
53 HOST = 'http://127.0.0.1'
54 PORT = 5000
54 PORT = 5000
55 BASE_URI = '%s:%s/' % (HOST, PORT)
55 BASE_URI = '%s:%s/' % (HOST, PORT)
56
56
57 if len(sys.argv) == 2:
57 if len(sys.argv) == 2:
58 BASE_URI = sys.argv[1]
58 BASE_URI = sys.argv[1]
59
59
60 if not BASE_URI.endswith('/'):
60 if not BASE_URI.endswith('/'):
61 BASE_URI += '/'
61 BASE_URI += '/'
62
62
63 print('Crawling @ %s' % BASE_URI)
63 print('Crawling @ %s' % BASE_URI)
64 BASE_URI += '%s'
64 BASE_URI += '%s'
65 PROJECT_PATH = os.path.join('/', 'home', 'username', 'repos')
65 PROJECT_PATH = os.path.join('/', 'home', 'username', 'repos')
66 PROJECTS = [
66 PROJECTS = [
67 # 'linux-magx-pbranch',
67 # 'linux-magx-pbranch',
68 'CPython',
68 'CPython',
69 'kallithea',
69 'kallithea',
70 ]
70 ]
71
71
72
72
73 cj = http.cookiejar.FileCookieJar(os.path.join(tempfile.gettempdir(), 'test_cookie.txt'))
73 cj = http.cookiejar.FileCookieJar(os.path.join(tempfile.gettempdir(), 'test_cookie.txt'))
74 o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
74 o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
75 o.addheaders = [
75 o.addheaders = [
76 ('User-agent', 'kallithea-crawler'),
76 ('User-agent', 'kallithea-crawler'),
77 ('Accept-Language', 'en - us, en;q = 0.5')
77 ('Accept-Language', 'en - us, en;q = 0.5')
78 ]
78 ]
79
79
80 urllib.request.install_opener(o)
80 urllib.request.install_opener(o)
81
81
82
82
83 def _get_repo(proj):
83 def _get_repo(proj):
84 if isinstance(proj, str):
84 if isinstance(proj, str):
85 repo = vcs.get_repo(os.path.join(PROJECT_PATH, proj))
85 repo = vcs.get_repo(os.path.join(PROJECT_PATH, proj))
86 proj = proj
87 else:
86 else:
88 repo = proj
87 repo = proj
89 proj = repo.name
88 proj = repo.name
90
89
91 return repo, proj
90 return repo, proj
92
91
93
92
94 def test_changelog_walk(proj, pages=100):
93 def test_changelog_walk(proj, pages=100):
95 repo, proj = _get_repo(proj)
94 repo, proj = _get_repo(proj)
96
95
97 total_time = 0
96 total_time = 0
98 for i in range(1, pages):
97 for i in range(1, pages):
99
98
100 page = '/'.join((proj, 'changelog',))
99 page = '/'.join((proj, 'changelog',))
101
100
102 full_uri = (BASE_URI % page) + '?' + urllib.parse.urlencode({'page': i})
101 full_uri = (BASE_URI % page) + '?' + urllib.parse.urlencode({'page': i})
103 s = time.time()
102 s = time.time()
104 f = o.open(full_uri)
103 f = o.open(full_uri)
105
104
106 assert f.url == full_uri, 'URL:%s does not match %s' % (f.url, full_uri)
105 assert f.url == full_uri, 'URL:%s does not match %s' % (f.url, full_uri)
107
106
108 size = len(f.read())
107 size = len(f.read())
109 e = time.time() - s
108 e = time.time() - s
110 total_time += e
109 total_time += e
111 print('visited %s size:%s req:%s ms' % (full_uri, size, e))
110 print('visited %s size:%s req:%s ms' % (full_uri, size, e))
112
111
113 print('total_time', total_time)
112 print('total_time', total_time)
114 print('average on req', total_time / float(pages))
113 print('average on req', total_time / float(pages))
115
114
116
115
117 def test_changeset_walk(proj, limit=None):
116 def test_changeset_walk(proj, limit=None):
118 repo, proj = _get_repo(proj)
117 repo, proj = _get_repo(proj)
119
118
120 print('processing', os.path.join(PROJECT_PATH, proj))
119 print('processing', os.path.join(PROJECT_PATH, proj))
121 total_time = 0
120 total_time = 0
122
121
123 cnt = 0
122 cnt = 0
124 for i in repo:
123 for i in repo:
125 cnt += 1
124 cnt += 1
126 raw_cs = '/'.join((proj, 'changeset', i.raw_id))
125 raw_cs = '/'.join((proj, 'changeset', i.raw_id))
127 if limit and limit == cnt:
126 if limit and limit == cnt:
128 break
127 break
129
128
130 full_uri = (BASE_URI % raw_cs)
129 full_uri = (BASE_URI % raw_cs)
131 print('%s visiting %s/%s' % (cnt, full_uri, i))
130 print('%s visiting %s/%s' % (cnt, full_uri, i))
132 s = time.time()
131 s = time.time()
133 f = o.open(full_uri)
132 f = o.open(full_uri)
134 size = len(f.read())
133 size = len(f.read())
135 e = time.time() - s
134 e = time.time() - s
136 total_time += e
135 total_time += e
137 print('%s visited %s/%s size:%s req:%s ms' % (cnt, full_uri, i, size, e))
136 print('%s visited %s/%s size:%s req:%s ms' % (cnt, full_uri, i, size, e))
138
137
139 print('total_time', total_time)
138 print('total_time', total_time)
140 print('average on req', total_time / float(cnt))
139 print('average on req', total_time / float(cnt))
141
140
142
141
143 def test_files_walk(proj, limit=100):
142 def test_files_walk(proj, limit=100):
144 repo, proj = _get_repo(proj)
143 repo, proj = _get_repo(proj)
145
144
146 print('processing', os.path.join(PROJECT_PATH, proj))
145 print('processing', os.path.join(PROJECT_PATH, proj))
147 total_time = 0
146 total_time = 0
148
147
149 paths_ = OrderedSet([''])
148 paths_ = OrderedSet([''])
150 try:
149 try:
151 tip = repo.get_changeset('tip')
150 tip = repo.get_changeset('tip')
152 for topnode, dirs, files in tip.walk('/'):
151 for topnode, dirs, files in tip.walk('/'):
153
152
154 for dir in dirs:
153 for dir in dirs:
155 paths_.add(dir.path)
154 paths_.add(dir.path)
156 for f in dir:
155 for f in dir:
157 paths_.add(f.path)
156 paths_.add(f.path)
158
157
159 for f in files:
158 for f in files:
160 paths_.add(f.path)
159 paths_.add(f.path)
161
160
162 except RepositoryError as e:
161 except RepositoryError as e:
163 pass
162 pass
164
163
165 cnt = 0
164 cnt = 0
166 for f in paths_:
165 for f in paths_:
167 cnt += 1
166 cnt += 1
168 if limit and limit == cnt:
167 if limit and limit == cnt:
169 break
168 break
170
169
171 file_path = '/'.join((proj, 'files', 'tip', f))
170 file_path = '/'.join((proj, 'files', 'tip', f))
172 full_uri = (BASE_URI % file_path)
171 full_uri = (BASE_URI % file_path)
173 print('%s visiting %s' % (cnt, full_uri))
172 print('%s visiting %s' % (cnt, full_uri))
174 s = time.time()
173 s = time.time()
175 f = o.open(full_uri)
174 f = o.open(full_uri)
176 size = len(f.read())
175 size = len(f.read())
177 e = time.time() - s
176 e = time.time() - s
178 total_time += e
177 total_time += e
179 print('%s visited OK size:%s req:%s ms' % (cnt, size, e))
178 print('%s visited OK size:%s req:%s ms' % (cnt, size, e))
180
179
181 print('total_time', total_time)
180 print('total_time', total_time)
182 print('average on req', total_time / float(cnt))
181 print('average on req', total_time / float(cnt))
183
182
184 if __name__ == '__main__':
183 if __name__ == '__main__':
185 for path in PROJECTS:
184 for path in PROJECTS:
186 repo = vcs.get_repo(os.path.join(PROJECT_PATH, path))
185 repo = vcs.get_repo(os.path.join(PROJECT_PATH, path))
187 for i in range(PASES):
186 for i in range(PASES):
188 print('PASS %s/%s' % (i, PASES))
187 print('PASS %s/%s' % (i, PASES))
189 test_changelog_walk(repo, pages=80)
188 test_changelog_walk(repo, pages=80)
190 test_changeset_walk(repo, limit=100)
189 test_changeset_walk(repo, limit=100)
191 test_files_walk(repo, limit=100)
190 test_files_walk(repo, limit=100)
General Comments 0
You need to be logged in to leave comments. Login now