##// END OF EJS Templates
api: add get_changesets
domruf -
r6619:19bc05bd default
parent child Browse files
Show More
@@ -1,1262 +1,1304 b''
1 .. _api:
1 .. _api:
2
2
3 ===
3 ===
4 API
4 API
5 ===
5 ===
6
6
7 Kallithea has a simple JSON RPC API with a single schema for calling all API
7 Kallithea has a simple JSON RPC API with a single schema for calling all API
8 methods. Everything is available by sending JSON encoded http(s) requests to
8 methods. Everything is available by sending JSON encoded http(s) requests to
9 ``<your_server>/_admin/api``.
9 ``<your_server>/_admin/api``.
10
10
11
11
12 API keys
12 API keys
13 --------
13 --------
14
14
15 Every Kallithea user automatically receives an API key, which they can
15 Every Kallithea user automatically receives an API key, which they can
16 view under "My Account". On this page, API keys can also be revoked, and
16 view under "My Account". On this page, API keys can also be revoked, and
17 additional API keys can be generated.
17 additional API keys can be generated.
18
18
19
19
20 API access
20 API access
21 ----------
21 ----------
22
22
23 Clients must send JSON encoded JSON-RPC requests::
23 Clients must send JSON encoded JSON-RPC requests::
24
24
25 {
25 {
26 "id: "<id>",
26 "id: "<id>",
27 "api_key": "<api_key>",
27 "api_key": "<api_key>",
28 "method": "<method_name>",
28 "method": "<method_name>",
29 "args": {"<arg_key>": "<arg_val>"}
29 "args": {"<arg_key>": "<arg_val>"}
30 }
30 }
31
31
32 For example, to pull to a local "CPython" mirror using curl::
32 For example, to pull to a local "CPython" mirror using curl::
33
33
34 curl https://kallithea.example.com/_admin/api -X POST -H 'content-type:text/plain' \
34 curl https://kallithea.example.com/_admin/api -X POST -H 'content-type:text/plain' \
35 --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
35 --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
36
36
37 In general, provide
37 In general, provide
38 - *id*, a value of any type, can be used to match the response with the request that it is replying to.
38 - *id*, a value of any type, can be used to match the response with the request that it is replying to.
39 - *api_key*, for authentication and permission validation.
39 - *api_key*, for authentication and permission validation.
40 - *method*, the name of the method to call -- a list of available methods can be found below.
40 - *method*, the name of the method to call -- a list of available methods can be found below.
41 - *args*, the arguments to pass to the method.
41 - *args*, the arguments to pass to the method.
42
42
43 .. note::
43 .. note::
44
44
45 api_key can be found or set on the user account page.
45 api_key can be found or set on the user account page.
46
46
47 The response to the JSON-RPC API call will always be a JSON structure::
47 The response to the JSON-RPC API call will always be a JSON structure::
48
48
49 {
49 {
50 "id": <id>, # the id that was used in the request
50 "id": <id>, # the id that was used in the request
51 "result": <result>|null, # JSON formatted result (null on error)
51 "result": <result>|null, # JSON formatted result (null on error)
52 "error": null|<error_message> # JSON formatted error (null on success)
52 "error": null|<error_message> # JSON formatted error (null on success)
53 }
53 }
54
54
55 All responses from the API will be ``HTTP/1.0 200 OK``. If an error occurs,
55 All responses from the API will be ``HTTP/1.0 200 OK``. If an error occurs,
56 the reponse will have a failure description in *error* and
56 the reponse will have a failure description in *error* and
57 *result* will be null.
57 *result* will be null.
58
58
59
59
60 API client
60 API client
61 ----------
61 ----------
62
62
63 Kallithea comes with a ``kallithea-api`` command line tool, providing a convenient
63 Kallithea comes with a ``kallithea-api`` command line tool, providing a convenient
64 way to call the JSON-RPC API.
64 way to call the JSON-RPC API.
65
65
66 For example, to call ``get_repo``::
66 For example, to call ``get_repo``::
67
67
68 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo
68 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo
69
69
70 Calling method get_repo => <Kallithea URL>
70 Calling method get_repo => <Kallithea URL>
71 Server response
71 Server response
72 ERROR:"Missing non optional `repoid` arg in JSON DATA"
72 ERROR:"Missing non optional `repoid` arg in JSON DATA"
73
73
74 Oops, looks like we forgot to add an argument. Let's try again, now
74 Oops, looks like we forgot to add an argument. Let's try again, now
75 providing the ``repoid`` as a parameter::
75 providing the ``repoid`` as a parameter::
76
76
77 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo repoid:myrepo
77 kallithea-api --apihost=<Kallithea URL> --apikey=<API key> get_repo repoid:myrepo
78
78
79 Calling method get_repo => <Kallithea URL>
79 Calling method get_repo => <Kallithea URL>
80 Server response
80 Server response
81 {
81 {
82 "clone_uri": null,
82 "clone_uri": null,
83 "created_on": "2015-08-31T14:55:19.042",
83 "created_on": "2015-08-31T14:55:19.042",
84 ...
84 ...
85
85
86 To avoid specifying ``apihost`` and ``apikey`` every time, run::
86 To avoid specifying ``apihost`` and ``apikey`` every time, run::
87
87
88 kallithea-api --save-config --apihost=<Kallithea URL> --apikey=<API key>
88 kallithea-api --save-config --apihost=<Kallithea URL> --apikey=<API key>
89
89
90 This will create a ``~/.config/kallithea`` with the specified URL and API key
90 This will create a ``~/.config/kallithea`` with the specified URL and API key
91 so you don't have to specify them every time.
91 so you don't have to specify them every time.
92
92
93
93
94 API methods
94 API methods
95 -----------
95 -----------
96
96
97
97
98 pull
98 pull
99 ^^^^
99 ^^^^
100
100
101 Pull the given repo from remote location. Can be used to automatically keep
101 Pull the given repo from remote location. Can be used to automatically keep
102 remote repos up to date.
102 remote repos up to date.
103 This command can only be executed using the api_key of a user with admin rights.
103 This command can only be executed using the api_key of a user with admin rights.
104
104
105 INPUT::
105 INPUT::
106
106
107 id : <id_for_response>
107 id : <id_for_response>
108 api_key : "<api_key>"
108 api_key : "<api_key>"
109 method : "pull"
109 method : "pull"
110 args : {
110 args : {
111 "repoid" : "<reponame or repo_id>"
111 "repoid" : "<reponame or repo_id>"
112 }
112 }
113
113
114 OUTPUT::
114 OUTPUT::
115
115
116 id : <id_given_in_input>
116 id : <id_given_in_input>
117 result : "Pulled from `<reponame>`"
117 result : "Pulled from `<reponame>`"
118 error : null
118 error : null
119
119
120 rescan_repos
120 rescan_repos
121 ^^^^^^^^^^^^
121 ^^^^^^^^^^^^
122
122
123 Rescan repositories. If ``remove_obsolete`` is set,
123 Rescan repositories. If ``remove_obsolete`` is set,
124 Kallithea will delete repos that are in the database but not in the filesystem.
124 Kallithea will delete repos that are in the database but not in the filesystem.
125 This command can only be executed using the api_key of a user with admin rights.
125 This command can only be executed using the api_key of a user with admin rights.
126
126
127 INPUT::
127 INPUT::
128
128
129 id : <id_for_response>
129 id : <id_for_response>
130 api_key : "<api_key>"
130 api_key : "<api_key>"
131 method : "rescan_repos"
131 method : "rescan_repos"
132 args : {
132 args : {
133 "remove_obsolete" : "<boolean = Optional(False)>"
133 "remove_obsolete" : "<boolean = Optional(False)>"
134 }
134 }
135
135
136 OUTPUT::
136 OUTPUT::
137
137
138 id : <id_given_in_input>
138 id : <id_given_in_input>
139 result : "{'added': [<list of names of added repos>],
139 result : "{'added': [<list of names of added repos>],
140 'removed': [<list of names of removed repos>]}"
140 'removed': [<list of names of removed repos>]}"
141 error : null
141 error : null
142
142
143 invalidate_cache
143 invalidate_cache
144 ^^^^^^^^^^^^^^^^
144 ^^^^^^^^^^^^^^^^
145
145
146 Invalidate the cache for a repository.
146 Invalidate the cache for a repository.
147 This command can only be executed using the api_key of a user with admin rights,
147 This command can only be executed using the api_key of a user with admin rights,
148 or that of a regular user with admin or write access to the repository.
148 or that of a regular user with admin or write access to the repository.
149
149
150 INPUT::
150 INPUT::
151
151
152 id : <id_for_response>
152 id : <id_for_response>
153 api_key : "<api_key>"
153 api_key : "<api_key>"
154 method : "invalidate_cache"
154 method : "invalidate_cache"
155 args : {
155 args : {
156 "repoid" : "<reponame or repo_id>"
156 "repoid" : "<reponame or repo_id>"
157 }
157 }
158
158
159 OUTPUT::
159 OUTPUT::
160
160
161 id : <id_given_in_input>
161 id : <id_given_in_input>
162 result : "Caches of repository `<reponame>`"
162 result : "Caches of repository `<reponame>`"
163 error : null
163 error : null
164
164
165 lock
165 lock
166 ^^^^
166 ^^^^
167
167
168 Set the locking state on the given repository by the given user.
168 Set the locking state on the given repository by the given user.
169 If the param ``userid`` is skipped, it is set to the ID of the user who is calling this method.
169 If the param ``userid`` is skipped, it is set to the ID of the user who is calling this method.
170 If param ``locked`` is skipped, the current lock state of the repository is returned.
170 If param ``locked`` is skipped, the current lock state of the repository is returned.
171 This command can only be executed using the api_key of a user with admin rights, or that of a regular user with admin or write access to the repository.
171 This command can only be executed using the api_key of a user with admin rights, or that of a regular user with admin or write access to the repository.
172
172
173 INPUT::
173 INPUT::
174
174
175 id : <id_for_response>
175 id : <id_for_response>
176 api_key : "<api_key>"
176 api_key : "<api_key>"
177 method : "lock"
177 method : "lock"
178 args : {
178 args : {
179 "repoid" : "<reponame or repo_id>"
179 "repoid" : "<reponame or repo_id>"
180 "userid" : "<user_id or username = Optional(=apiuser)>",
180 "userid" : "<user_id or username = Optional(=apiuser)>",
181 "locked" : "<bool true|false = Optional(=None)>"
181 "locked" : "<bool true|false = Optional(=None)>"
182 }
182 }
183
183
184 OUTPUT::
184 OUTPUT::
185
185
186 id : <id_given_in_input>
186 id : <id_given_in_input>
187 result : {
187 result : {
188 "repo": "<reponame>",
188 "repo": "<reponame>",
189 "locked": "<bool true|false>",
189 "locked": "<bool true|false>",
190 "locked_since": "<float lock_time>",
190 "locked_since": "<float lock_time>",
191 "locked_by": "<username>",
191 "locked_by": "<username>",
192 "msg": "User `<username>` set lock state for repo `<reponame>` to `<false|true>`"
192 "msg": "User `<username>` set lock state for repo `<reponame>` to `<false|true>`"
193 }
193 }
194 error : null
194 error : null
195
195
196 get_ip
196 get_ip
197 ^^^^^^
197 ^^^^^^
198
198
199 Return IP address as seen from Kallithea server, together with all
199 Return IP address as seen from Kallithea server, together with all
200 defined IP addresses for given user.
200 defined IP addresses for given user.
201 This command can only be executed using the api_key of a user with admin rights.
201 This command can only be executed using the api_key of a user with admin rights.
202
202
203 INPUT::
203 INPUT::
204
204
205 id : <id_for_response>
205 id : <id_for_response>
206 api_key : "<api_key>"
206 api_key : "<api_key>"
207 method : "get_ip"
207 method : "get_ip"
208 args : {
208 args : {
209 "userid" : "<user_id or username>",
209 "userid" : "<user_id or username>",
210 }
210 }
211
211
212 OUTPUT::
212 OUTPUT::
213
213
214 id : <id_given_in_input>
214 id : <id_given_in_input>
215 result : {
215 result : {
216 "ip_addr_server": <ip_from_clien>",
216 "ip_addr_server": <ip_from_clien>",
217 "user_ips": [
217 "user_ips": [
218 {
218 {
219 "ip_addr": "<ip_with_mask>",
219 "ip_addr": "<ip_with_mask>",
220 "ip_range": ["<start_ip>", "<end_ip>"],
220 "ip_range": ["<start_ip>", "<end_ip>"],
221 },
221 },
222 ...
222 ...
223 ]
223 ]
224 }
224 }
225
225
226 error : null
226 error : null
227
227
228 get_user
228 get_user
229 ^^^^^^^^
229 ^^^^^^^^
230
230
231 Get a user by username or userid. The result is empty if user can't be found.
231 Get a user by username or userid. The result is empty if user can't be found.
232 If userid param is skipped, it is set to id of user who is calling this method.
232 If userid param is skipped, it is set to id of user who is calling this method.
233 Any userid can be specified when the command is executed using the api_key of a user with admin rights.
233 Any userid can be specified when the command is executed using the api_key of a user with admin rights.
234 Regular users can only specify their own userid.
234 Regular users can only specify their own userid.
235
235
236 INPUT::
236 INPUT::
237
237
238 id : <id_for_response>
238 id : <id_for_response>
239 api_key : "<api_key>"
239 api_key : "<api_key>"
240 method : "get_user"
240 method : "get_user"
241 args : {
241 args : {
242 "userid" : "<username or user_id Optional(=apiuser)>"
242 "userid" : "<username or user_id Optional(=apiuser)>"
243 }
243 }
244
244
245 OUTPUT::
245 OUTPUT::
246
246
247 id : <id_given_in_input>
247 id : <id_given_in_input>
248 result: None if user does not exist or
248 result: None if user does not exist or
249 {
249 {
250 "user_id" : "<user_id>",
250 "user_id" : "<user_id>",
251 "api_key" : "<api_key>",
251 "api_key" : "<api_key>",
252 "username" : "<username>",
252 "username" : "<username>",
253 "firstname": "<firstname>",
253 "firstname": "<firstname>",
254 "lastname" : "<lastname>",
254 "lastname" : "<lastname>",
255 "email" : "<email>",
255 "email" : "<email>",
256 "emails": "<list_of_all_additional_emails>",
256 "emails": "<list_of_all_additional_emails>",
257 "ip_addresses": "<list_of_ip_addresses_for_user>",
257 "ip_addresses": "<list_of_ip_addresses_for_user>",
258 "active" : "<bool>",
258 "active" : "<bool>",
259 "admin" :Β  "<bool>",
259 "admin" :Β  "<bool>",
260 "ldap_dn" : "<ldap_dn>",
260 "ldap_dn" : "<ldap_dn>",
261 "last_login": "<last_login>",
261 "last_login": "<last_login>",
262 "permissions": {
262 "permissions": {
263 "global": ["hg.create.repository",
263 "global": ["hg.create.repository",
264 "repository.read",
264 "repository.read",
265 "hg.register.manual_activate"],
265 "hg.register.manual_activate"],
266 "repositories": {"repo1": "repository.none"},
266 "repositories": {"repo1": "repository.none"},
267 "repositories_groups": {"Group1": "group.read"}
267 "repositories_groups": {"Group1": "group.read"}
268 },
268 },
269 }
269 }
270 error: null
270 error: null
271
271
272 get_users
272 get_users
273 ^^^^^^^^^
273 ^^^^^^^^^
274
274
275 List all existing users.
275 List all existing users.
276 This command can only be executed using the api_key of a user with admin rights.
276 This command can only be executed using the api_key of a user with admin rights.
277
277
278 INPUT::
278 INPUT::
279
279
280 id : <id_for_response>
280 id : <id_for_response>
281 api_key : "<api_key>"
281 api_key : "<api_key>"
282 method : "get_users"
282 method : "get_users"
283 args : { }
283 args : { }
284
284
285 OUTPUT::
285 OUTPUT::
286
286
287 id : <id_given_in_input>
287 id : <id_given_in_input>
288 result: [
288 result: [
289 {
289 {
290 "user_id" : "<user_id>",
290 "user_id" : "<user_id>",
291 "api_key" : "<api_key>",
291 "api_key" : "<api_key>",
292 "username" : "<username>",
292 "username" : "<username>",
293 "firstname": "<firstname>",
293 "firstname": "<firstname>",
294 "lastname" : "<lastname>",
294 "lastname" : "<lastname>",
295 "email" : "<email>",
295 "email" : "<email>",
296 "emails": "<list_of_all_additional_emails>",
296 "emails": "<list_of_all_additional_emails>",
297 "ip_addresses": "<list_of_ip_addresses_for_user>",
297 "ip_addresses": "<list_of_ip_addresses_for_user>",
298 "active" : "<bool>",
298 "active" : "<bool>",
299 "admin" :Β  "<bool>",
299 "admin" :Β  "<bool>",
300 "ldap_dn" : "<ldap_dn>",
300 "ldap_dn" : "<ldap_dn>",
301 "last_login": "<last_login>",
301 "last_login": "<last_login>",
302 },
302 },
303 …
303 …
304 ]
304 ]
305 error: null
305 error: null
306
306
307 .. _create-user:
307 .. _create-user:
308
308
309 create_user
309 create_user
310 ^^^^^^^^^^^
310 ^^^^^^^^^^^
311
311
312 Create new user.
312 Create new user.
313 This command can only be executed using the api_key of a user with admin rights.
313 This command can only be executed using the api_key of a user with admin rights.
314
314
315 INPUT::
315 INPUT::
316
316
317 id : <id_for_response>
317 id : <id_for_response>
318 api_key : "<api_key>"
318 api_key : "<api_key>"
319 method : "create_user"
319 method : "create_user"
320 args : {
320 args : {
321 "username" : "<username>",
321 "username" : "<username>",
322 "email" : "<useremail>",
322 "email" : "<useremail>",
323 "password" : "<password = Optional(None)>",
323 "password" : "<password = Optional(None)>",
324 "firstname" : "<firstname> = Optional(None)",
324 "firstname" : "<firstname> = Optional(None)",
325 "lastname" : "<lastname> = Optional(None)",
325 "lastname" : "<lastname> = Optional(None)",
326 "active" : "<bool> = Optional(True)",
326 "active" : "<bool> = Optional(True)",
327 "admin" : "<bool> = Optional(False)",
327 "admin" : "<bool> = Optional(False)",
328 "ldap_dn" : "<ldap_dn> = Optional(None)"
328 "ldap_dn" : "<ldap_dn> = Optional(None)"
329 }
329 }
330
330
331 OUTPUT::
331 OUTPUT::
332
332
333 id : <id_given_in_input>
333 id : <id_given_in_input>
334 result: {
334 result: {
335 "msg" : "created new user `<username>`",
335 "msg" : "created new user `<username>`",
336 "user": {
336 "user": {
337 "user_id" : "<user_id>",
337 "user_id" : "<user_id>",
338 "username" : "<username>",
338 "username" : "<username>",
339 "firstname": "<firstname>",
339 "firstname": "<firstname>",
340 "lastname" : "<lastname>",
340 "lastname" : "<lastname>",
341 "email" : "<email>",
341 "email" : "<email>",
342 "emails": "<list_of_all_additional_emails>",
342 "emails": "<list_of_all_additional_emails>",
343 "active" : "<bool>",
343 "active" : "<bool>",
344 "admin" :Β  "<bool>",
344 "admin" :Β  "<bool>",
345 "ldap_dn" : "<ldap_dn>",
345 "ldap_dn" : "<ldap_dn>",
346 "last_login": "<last_login>",
346 "last_login": "<last_login>",
347 },
347 },
348 }
348 }
349 error: null
349 error: null
350
350
351 Example::
351 Example::
352
352
353 kallithea-api create_user username:bent email:bent@example.com firstname:Bent lastname:Bentsen extern_type:ldap extern_name:uid=bent,dc=example,dc=com
353 kallithea-api create_user username:bent email:bent@example.com firstname:Bent lastname:Bentsen extern_type:ldap extern_name:uid=bent,dc=example,dc=com
354
354
355 update_user
355 update_user
356 ^^^^^^^^^^^
356 ^^^^^^^^^^^
357
357
358 Update the given user if such user exists.
358 Update the given user if such user exists.
359 This command can only be executed using the api_key of a user with admin rights.
359 This command can only be executed using the api_key of a user with admin rights.
360
360
361 INPUT::
361 INPUT::
362
362
363 id : <id_for_response>
363 id : <id_for_response>
364 api_key : "<api_key>"
364 api_key : "<api_key>"
365 method : "update_user"
365 method : "update_user"
366 args : {
366 args : {
367 "userid" : "<user_id or username>",
367 "userid" : "<user_id or username>",
368 "username" : "<username> = Optional(None)",
368 "username" : "<username> = Optional(None)",
369 "email" : "<useremail> = Optional(None)",
369 "email" : "<useremail> = Optional(None)",
370 "password" : "<password> = Optional(None)",
370 "password" : "<password> = Optional(None)",
371 "firstname" : "<firstname> = Optional(None)",
371 "firstname" : "<firstname> = Optional(None)",
372 "lastname" : "<lastname> = Optional(None)",
372 "lastname" : "<lastname> = Optional(None)",
373 "active" : "<bool> = Optional(None)",
373 "active" : "<bool> = Optional(None)",
374 "admin" : "<bool> = Optional(None)",
374 "admin" : "<bool> = Optional(None)",
375 "ldap_dn" : "<ldap_dn> = Optional(None)"
375 "ldap_dn" : "<ldap_dn> = Optional(None)"
376 }
376 }
377
377
378 OUTPUT::
378 OUTPUT::
379
379
380 id : <id_given_in_input>
380 id : <id_given_in_input>
381 result: {
381 result: {
382 "msg" : "updated user ID:<userid> <username>",
382 "msg" : "updated user ID:<userid> <username>",
383 "user": {
383 "user": {
384 "user_id" : "<user_id>",
384 "user_id" : "<user_id>",
385 "api_key" : "<api_key>",
385 "api_key" : "<api_key>",
386 "username" : "<username>",
386 "username" : "<username>",
387 "firstname": "<firstname>",
387 "firstname": "<firstname>",
388 "lastname" : "<lastname>",
388 "lastname" : "<lastname>",
389 "email" : "<email>",
389 "email" : "<email>",
390 "emails": "<list_of_all_additional_emails>",
390 "emails": "<list_of_all_additional_emails>",
391 "active" : "<bool>",
391 "active" : "<bool>",
392 "admin" :Β  "<bool>",
392 "admin" :Β  "<bool>",
393 "ldap_dn" : "<ldap_dn>",
393 "ldap_dn" : "<ldap_dn>",
394 "last_login": "<last_login>",
394 "last_login": "<last_login>",
395 },
395 },
396 }
396 }
397 error: null
397 error: null
398
398
399 delete_user
399 delete_user
400 ^^^^^^^^^^^
400 ^^^^^^^^^^^
401
401
402 Delete the given user if such a user exists.
402 Delete the given user if such a user exists.
403 This command can only be executed using the api_key of a user with admin rights.
403 This command can only be executed using the api_key of a user with admin rights.
404
404
405 INPUT::
405 INPUT::
406
406
407 id : <id_for_response>
407 id : <id_for_response>
408 api_key : "<api_key>"
408 api_key : "<api_key>"
409 method : "delete_user"
409 method : "delete_user"
410 args : {
410 args : {
411 "userid" : "<user_id or username>",
411 "userid" : "<user_id or username>",
412 }
412 }
413
413
414 OUTPUT::
414 OUTPUT::
415
415
416 id : <id_given_in_input>
416 id : <id_given_in_input>
417 result: {
417 result: {
418 "msg" : "deleted user ID:<userid> <username>",
418 "msg" : "deleted user ID:<userid> <username>",
419 "user": null
419 "user": null
420 }
420 }
421 error: null
421 error: null
422
422
423 get_user_group
423 get_user_group
424 ^^^^^^^^^^^^^^
424 ^^^^^^^^^^^^^^
425
425
426 Get an existing user group.
426 Get an existing user group.
427 This command can only be executed using the api_key of a user with admin rights.
427 This command can only be executed using the api_key of a user with admin rights.
428
428
429 INPUT::
429 INPUT::
430
430
431 id : <id_for_response>
431 id : <id_for_response>
432 api_key : "<api_key>"
432 api_key : "<api_key>"
433 method : "get_user_group"
433 method : "get_user_group"
434 args : {
434 args : {
435 "usergroupid" : "<user group id or name>"
435 "usergroupid" : "<user group id or name>"
436 }
436 }
437
437
438 OUTPUT::
438 OUTPUT::
439
439
440 id : <id_given_in_input>
440 id : <id_given_in_input>
441 result : None if group not exist
441 result : None if group not exist
442 {
442 {
443 "users_group_id" : "<id>",
443 "users_group_id" : "<id>",
444 "group_name" : "<groupname>",
444 "group_name" : "<groupname>",
445 "active": "<bool>",
445 "active": "<bool>",
446 "members" : [
446 "members" : [
447 {
447 {
448 "user_id" : "<user_id>",
448 "user_id" : "<user_id>",
449 "api_key" : "<api_key>",
449 "api_key" : "<api_key>",
450 "username" : "<username>",
450 "username" : "<username>",
451 "firstname": "<firstname>",
451 "firstname": "<firstname>",
452 "lastname" : "<lastname>",
452 "lastname" : "<lastname>",
453 "email" : "<email>",
453 "email" : "<email>",
454 "emails": "<list_of_all_additional_emails>",
454 "emails": "<list_of_all_additional_emails>",
455 "active" : "<bool>",
455 "active" : "<bool>",
456 "admin" :Β  "<bool>",
456 "admin" :Β  "<bool>",
457 "ldap_dn" : "<ldap_dn>",
457 "ldap_dn" : "<ldap_dn>",
458 "last_login": "<last_login>",
458 "last_login": "<last_login>",
459 },
459 },
460 …
460 …
461 ]
461 ]
462 }
462 }
463 error : null
463 error : null
464
464
465 get_user_groups
465 get_user_groups
466 ^^^^^^^^^^^^^^^
466 ^^^^^^^^^^^^^^^
467
467
468 List all existing user groups.
468 List all existing user groups.
469 This command can only be executed using the api_key of a user with admin rights.
469 This command can only be executed using the api_key of a user with admin rights.
470
470
471 INPUT::
471 INPUT::
472
472
473 id : <id_for_response>
473 id : <id_for_response>
474 api_key : "<api_key>"
474 api_key : "<api_key>"
475 method : "get_user_groups"
475 method : "get_user_groups"
476 args : { }
476 args : { }
477
477
478 OUTPUT::
478 OUTPUT::
479
479
480 id : <id_given_in_input>
480 id : <id_given_in_input>
481 result : [
481 result : [
482 {
482 {
483 "users_group_id" : "<id>",
483 "users_group_id" : "<id>",
484 "group_name" : "<groupname>",
484 "group_name" : "<groupname>",
485 "active": "<bool>",
485 "active": "<bool>",
486 },
486 },
487 …
487 …
488 ]
488 ]
489 error : null
489 error : null
490
490
491 create_user_group
491 create_user_group
492 ^^^^^^^^^^^^^^^^^
492 ^^^^^^^^^^^^^^^^^
493
493
494 Create a new user group.
494 Create a new user group.
495 This command can only be executed using the api_key of a user with admin rights.
495 This command can only be executed using the api_key of a user with admin rights.
496
496
497 INPUT::
497 INPUT::
498
498
499 id : <id_for_response>
499 id : <id_for_response>
500 api_key : "<api_key>"
500 api_key : "<api_key>"
501 method : "create_user_group"
501 method : "create_user_group"
502 args: {
502 args: {
503 "group_name": "<groupname>",
503 "group_name": "<groupname>",
504 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
504 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
505 "active": "<bool> = Optional(True)"
505 "active": "<bool> = Optional(True)"
506 }
506 }
507
507
508 OUTPUT::
508 OUTPUT::
509
509
510 id : <id_given_in_input>
510 id : <id_given_in_input>
511 result: {
511 result: {
512 "msg": "created new user group `<groupname>`",
512 "msg": "created new user group `<groupname>`",
513 "users_group": {
513 "users_group": {
514 "users_group_id" : "<id>",
514 "users_group_id" : "<id>",
515 "group_name" : "<groupname>",
515 "group_name" : "<groupname>",
516 "active": "<bool>",
516 "active": "<bool>",
517 },
517 },
518 }
518 }
519 error: null
519 error: null
520
520
521 add_user_to_user_group
521 add_user_to_user_group
522 ^^^^^^^^^^^^^^^^^^^^^^
522 ^^^^^^^^^^^^^^^^^^^^^^
523
523
524 Adds a user to a user group. If the user already is in that group, success will be
524 Adds a user to a user group. If the user already is in that group, success will be
525 ``false``.
525 ``false``.
526 This command can only be executed using the api_key of a user with admin rights.
526 This command can only be executed using the api_key of a user with admin rights.
527
527
528 INPUT::
528 INPUT::
529
529
530 id : <id_for_response>
530 id : <id_for_response>
531 api_key : "<api_key>"
531 api_key : "<api_key>"
532 method : "add_user_user_group"
532 method : "add_user_user_group"
533 args: {
533 args: {
534 "usersgroupid" : "<user group id or name>",
534 "usersgroupid" : "<user group id or name>",
535 "userid" : "<user_id or username>",
535 "userid" : "<user_id or username>",
536 }
536 }
537
537
538 OUTPUT::
538 OUTPUT::
539
539
540 id : <id_given_in_input>
540 id : <id_given_in_input>
541 result: {
541 result: {
542 "success": True|False # depends on if member is in group
542 "success": True|False # depends on if member is in group
543 "msg": "added member `<username>` to a user group `<groupname>` |
543 "msg": "added member `<username>` to a user group `<groupname>` |
544 User is already in that group"
544 User is already in that group"
545 }
545 }
546 error: null
546 error: null
547
547
548 remove_user_from_user_group
548 remove_user_from_user_group
549 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
549 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
550
550
551 Remove a user from a user group. If the user isn't in the given group, success will
551 Remove a user from a user group. If the user isn't in the given group, success will
552 be ``false``.
552 be ``false``.
553 This command can only be executed using the api_key of a user with admin rights.
553 This command can only be executed using the api_key of a user with admin rights.
554
554
555 INPUT::
555 INPUT::
556
556
557 id : <id_for_response>
557 id : <id_for_response>
558 api_key : "<api_key>"
558 api_key : "<api_key>"
559 method : "remove_user_from_user_group"
559 method : "remove_user_from_user_group"
560 args: {
560 args: {
561 "usersgroupid" : "<user group id or name>",
561 "usersgroupid" : "<user group id or name>",
562 "userid" : "<user_id or username>",
562 "userid" : "<user_id or username>",
563 }
563 }
564
564
565 OUTPUT::
565 OUTPUT::
566
566
567 id : <id_given_in_input>
567 id : <id_given_in_input>
568 result: {
568 result: {
569 "success": True|False, # depends on if member is in group
569 "success": True|False, # depends on if member is in group
570 "msg": "removed member <username> from user group <groupname> |
570 "msg": "removed member <username> from user group <groupname> |
571 User wasn't in group"
571 User wasn't in group"
572 }
572 }
573 error: null
573 error: null
574
574
575 get_repo
575 get_repo
576 ^^^^^^^^
576 ^^^^^^^^
577
577
578 Get an existing repository by its name or repository_id. Members will contain
578 Get an existing repository by its name or repository_id. Members will contain
579 either users_group or users associated to that repository.
579 either users_group or users associated to that repository.
580 This command can only be executed using the api_key of a user with admin rights,
580 This command can only be executed using the api_key of a user with admin rights,
581 or that of a regular user with at least read access to the repository.
581 or that of a regular user with at least read access to the repository.
582
582
583 INPUT::
583 INPUT::
584
584
585 id : <id_for_response>
585 id : <id_for_response>
586 api_key : "<api_key>"
586 api_key : "<api_key>"
587 method : "get_repo"
587 method : "get_repo"
588 args: {
588 args: {
589 "repoid" : "<reponame or repo_id>",
589 "repoid" : "<reponame or repo_id>",
590 "with_revision_names": "<bool> = Optional(False)",
590 "with_revision_names": "<bool> = Optional(False)",
591 "with_pullrequests": "<bool> = Optional(False)",
591 "with_pullrequests": "<bool> = Optional(False)",
592 }
592 }
593
593
594 OUTPUT::
594 OUTPUT::
595
595
596 id : <id_given_in_input>
596 id : <id_given_in_input>
597 result: None if repository does not exist or
597 result: None if repository does not exist or
598 {
598 {
599 "repo_id" : "<repo_id>",
599 "repo_id" : "<repo_id>",
600 "repo_name" : "<reponame>"
600 "repo_name" : "<reponame>"
601 "repo_type" : "<repo_type>",
601 "repo_type" : "<repo_type>",
602 "clone_uri" : "<clone_uri>",
602 "clone_uri" : "<clone_uri>",
603 "enable_downloads": "<bool>",
603 "enable_downloads": "<bool>",
604 "enable_locking": "<bool>",
604 "enable_locking": "<bool>",
605 "enable_statistics": "<bool>",
605 "enable_statistics": "<bool>",
606 "private": "<bool>",
606 "private": "<bool>",
607 "created_on" : "<date_time_created>",
607 "created_on" : "<date_time_created>",
608 "description" : "<description>",
608 "description" : "<description>",
609 "landing_rev": "<landing_rev>",
609 "landing_rev": "<landing_rev>",
610 "last_changeset": {
610 "last_changeset": {
611 "author": "<full_author>",
611 "author": "<full_author>",
612 "date": "<date_time_of_commit>",
612 "date": "<date_time_of_commit>",
613 "message": "<commit_message>",
613 "message": "<commit_message>",
614 "raw_id": "<raw_id>",
614 "raw_id": "<raw_id>",
615 "revision": "<numeric_revision>",
615 "revision": "<numeric_revision>",
616 "short_id": "<short_id>"
616 "short_id": "<short_id>"
617 },
617 },
618 "owner": "<repo_owner>",
618 "owner": "<repo_owner>",
619 "fork_of": "<name_of_fork_parent>",
619 "fork_of": "<name_of_fork_parent>",
620 "members" : [
620 "members" : [
621 {
621 {
622 "type": "user",
622 "type": "user",
623 "user_id" : "<user_id>",
623 "user_id" : "<user_id>",
624 "api_key" : "<api_key>",
624 "api_key" : "<api_key>",
625 "username" : "<username>",
625 "username" : "<username>",
626 "firstname": "<firstname>",
626 "firstname": "<firstname>",
627 "lastname" : "<lastname>",
627 "lastname" : "<lastname>",
628 "email" : "<email>",
628 "email" : "<email>",
629 "emails": "<list_of_all_additional_emails>",
629 "emails": "<list_of_all_additional_emails>",
630 "active" : "<bool>",
630 "active" : "<bool>",
631 "admin" :Β  "<bool>",
631 "admin" :Β  "<bool>",
632 "ldap_dn" : "<ldap_dn>",
632 "ldap_dn" : "<ldap_dn>",
633 "last_login": "<last_login>",
633 "last_login": "<last_login>",
634 "permission" : "repository.(read|write|admin)"
634 "permission" : "repository.(read|write|admin)"
635 },
635 },
636 …
636 …
637 {
637 {
638 "type": "users_group",
638 "type": "users_group",
639 "id" : "<usersgroupid>",
639 "id" : "<usersgroupid>",
640 "name" : "<usersgroupname>",
640 "name" : "<usersgroupname>",
641 "active": "<bool>",
641 "active": "<bool>",
642 "permission" : "repository.(read|write|admin)"
642 "permission" : "repository.(read|write|admin)"
643 },
643 },
644 …
644 …
645 ],
645 ],
646 "followers": [
646 "followers": [
647 {
647 {
648 "user_id" : "<user_id>",
648 "user_id" : "<user_id>",
649 "username" : "<username>",
649 "username" : "<username>",
650 "api_key" : "<api_key>",
650 "api_key" : "<api_key>",
651 "firstname": "<firstname>",
651 "firstname": "<firstname>",
652 "lastname" : "<lastname>",
652 "lastname" : "<lastname>",
653 "email" : "<email>",
653 "email" : "<email>",
654 "emails": "<list_of_all_additional_emails>",
654 "emails": "<list_of_all_additional_emails>",
655 "ip_addresses": "<list_of_ip_addresses_for_user>",
655 "ip_addresses": "<list_of_ip_addresses_for_user>",
656 "active" : "<bool>",
656 "active" : "<bool>",
657 "admin" :Β  "<bool>",
657 "admin" :Β  "<bool>",
658 "ldap_dn" : "<ldap_dn>",
658 "ldap_dn" : "<ldap_dn>",
659 "last_login": "<last_login>",
659 "last_login": "<last_login>",
660 },
660 },
661 …
661 …
662 ],
662 ],
663 <if with_revision_names == True>
663 <if with_revision_names == True>
664 "tags": {
664 "tags": {
665 "<tagname>": "<raw_id>",
665 "<tagname>": "<raw_id>",
666 ...
666 ...
667 },
667 },
668 "branches": {
668 "branches": {
669 "<branchname>": "<raw_id>",
669 "<branchname>": "<raw_id>",
670 ...
670 ...
671 },
671 },
672 "bookmarks": {
672 "bookmarks": {
673 "<bookmarkname>": "<raw_id>",
673 "<bookmarkname>": "<raw_id>",
674 ...
674 ...
675 },
675 },
676 <if with_pullrequests == True>
676 <if with_pullrequests == True>
677 "pull_requests": [
677 "pull_requests": [
678 {
678 {
679 "status": "<pull_request_status>",
679 "status": "<pull_request_status>",
680 "pull_request_id": <pull_request_id>,
680 "pull_request_id": <pull_request_id>,
681 "description": "<pull_request_description>",
681 "description": "<pull_request_description>",
682 "title": "<pull_request_title>",
682 "title": "<pull_request_title>",
683 "url": "<pull_request_url>",
683 "url": "<pull_request_url>",
684 "reviewers": [
684 "reviewers": [
685 {
685 {
686 "username": "<user_id>",
686 "username": "<user_id>",
687 },
687 },
688 ...
688 ...
689 ],
689 ],
690 "org_repo_url": "<repo_url>",
690 "org_repo_url": "<repo_url>",
691 "org_ref_parts": [
691 "org_ref_parts": [
692 "<ref_type>",
692 "<ref_type>",
693 "<ref_name>",
693 "<ref_name>",
694 "<raw_id>"
694 "<raw_id>"
695 ],
695 ],
696 "other_ref_parts": [
696 "other_ref_parts": [
697 "<ref_type>",
697 "<ref_type>",
698 "<ref_name>",
698 "<ref_name>",
699 "<raw_id>"
699 "<raw_id>"
700 ],
700 ],
701 "comments": [
701 "comments": [
702 {
702 {
703 "username": "<user_id>",
703 "username": "<user_id>",
704 "text": "<comment text>",
704 "text": "<comment text>",
705 "comment_id": "<comment_id>",
705 "comment_id": "<comment_id>",
706 },
706 },
707 ...
707 ...
708 ],
708 ],
709 "owner": "<username>",
709 "owner": "<username>",
710 "statuses": [
710 "statuses": [
711 {
711 {
712 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
712 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
713 "reviewer": "<user_id>",
713 "reviewer": "<user_id>",
714 "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
714 "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
715 },
715 },
716 ...
716 ...
717 ],
717 ],
718 "revisions": [
718 "revisions": [
719 "<raw_id>",
719 "<raw_id>",
720 ...
720 ...
721 ]
721 ]
722 },
722 },
723 ...
723 ...
724 ]
724 ]
725 }
725 }
726 error: null
726 error: null
727
727
728 get_repos
728 get_repos
729 ^^^^^^^^^
729 ^^^^^^^^^
730
730
731 List all existing repositories.
731 List all existing repositories.
732 This command can only be executed using the api_key of a user with admin rights,
732 This command can only be executed using the api_key of a user with admin rights,
733 or that of a regular user with at least read access to the repository.
733 or that of a regular user with at least read access to the repository.
734
734
735 INPUT::
735 INPUT::
736
736
737 id : <id_for_response>
737 id : <id_for_response>
738 api_key : "<api_key>"
738 api_key : "<api_key>"
739 method : "get_repos"
739 method : "get_repos"
740 args: { }
740 args: { }
741
741
742 OUTPUT::
742 OUTPUT::
743
743
744 id : <id_given_in_input>
744 id : <id_given_in_input>
745 result: [
745 result: [
746 {
746 {
747 "repo_id" : "<repo_id>",
747 "repo_id" : "<repo_id>",
748 "repo_name" : "<reponame>"
748 "repo_name" : "<reponame>"
749 "repo_type" : "<repo_type>",
749 "repo_type" : "<repo_type>",
750 "clone_uri" : "<clone_uri>",
750 "clone_uri" : "<clone_uri>",
751 "private" : "<bool>",
751 "private" : "<bool>",
752 "created_on" : "<datetimecreated>",
752 "created_on" : "<datetimecreated>",
753 "description" : "<description>",
753 "description" : "<description>",
754 "landing_rev": "<landing_rev>",
754 "landing_rev": "<landing_rev>",
755 "owner": "<repo_owner>",
755 "owner": "<repo_owner>",
756 "fork_of": "<name_of_fork_parent>",
756 "fork_of": "<name_of_fork_parent>",
757 "enable_downloads": "<bool>",
757 "enable_downloads": "<bool>",
758 "enable_locking": "<bool>",
758 "enable_locking": "<bool>",
759 "enable_statistics": "<bool>",
759 "enable_statistics": "<bool>",
760 },
760 },
761 …
761 …
762 ]
762 ]
763 error: null
763 error: null
764
764
765 get_repo_nodes
765 get_repo_nodes
766 ^^^^^^^^^^^^^^
766 ^^^^^^^^^^^^^^
767
767
768 Return a list of files and directories for a given path at the given revision.
768 Return a list of files and directories for a given path at the given revision.
769 It is possible to specify ret_type to show only ``files`` or ``dirs``.
769 It is possible to specify ret_type to show only ``files`` or ``dirs``.
770 This command can only be executed using the api_key of a user with admin rights.
770 This command can only be executed using the api_key of a user with admin rights.
771
771
772 INPUT::
772 INPUT::
773
773
774 id : <id_for_response>
774 id : <id_for_response>
775 api_key : "<api_key>"
775 api_key : "<api_key>"
776 method : "get_repo_nodes"
776 method : "get_repo_nodes"
777 args: {
777 args: {
778 "repoid" : "<reponame or repo_id>"
778 "repoid" : "<reponame or repo_id>"
779 "revision" : "<revision>",
779 "revision" : "<revision>",
780 "root_path" : "<root_path>",
780 "root_path" : "<root_path>",
781 "ret_type" : "<ret_type> = Optional('all')"
781 "ret_type" : "<ret_type> = Optional('all')"
782 }
782 }
783
783
784 OUTPUT::
784 OUTPUT::
785
785
786 id : <id_given_in_input>
786 id : <id_given_in_input>
787 result: [
787 result: [
788 {
788 {
789 "name" : "<name>"
789 "name" : "<name>"
790 "type" : "<type>",
790 "type" : "<type>",
791 },
791 },
792 …
792 …
793 ]
793 ]
794 error: null
794 error: null
795
795
796 create_repo
796 create_repo
797 ^^^^^^^^^^^
797 ^^^^^^^^^^^
798
798
799 Create a repository. If the repository name contains "/", all needed repository
799 Create a repository. If the repository name contains "/", all needed repository
800 groups will be created. For example "foo/bar/baz" will create repository groups
800 groups will be created. For example "foo/bar/baz" will create repository groups
801 "foo", "bar" (with "foo" as parent), and create "baz" repository with
801 "foo", "bar" (with "foo" as parent), and create "baz" repository with
802 "bar" as group.
802 "bar" as group.
803 This command can only be executed using the api_key of a user with admin rights,
803 This command can only be executed using the api_key of a user with admin rights,
804 or that of a regular user with create repository permission.
804 or that of a regular user with create repository permission.
805 Regular users cannot specify owner parameter.
805 Regular users cannot specify owner parameter.
806
806
807 INPUT::
807 INPUT::
808
808
809 id : <id_for_response>
809 id : <id_for_response>
810 api_key : "<api_key>"
810 api_key : "<api_key>"
811 method : "create_repo"
811 method : "create_repo"
812 args: {
812 args: {
813 "repo_name" : "<reponame>",
813 "repo_name" : "<reponame>",
814 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
814 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
815 "repo_type" : "<repo_type> = Optional('hg')",
815 "repo_type" : "<repo_type> = Optional('hg')",
816 "description" : "<description> = Optional('')",
816 "description" : "<description> = Optional('')",
817 "private" : "<bool> = Optional(False)",
817 "private" : "<bool> = Optional(False)",
818 "clone_uri" : "<clone_uri> = Optional(None)",
818 "clone_uri" : "<clone_uri> = Optional(None)",
819 "landing_rev" : "<landing_rev> = Optional('tip')",
819 "landing_rev" : "<landing_rev> = Optional('tip')",
820 "enable_downloads": "<bool> = Optional(False)",
820 "enable_downloads": "<bool> = Optional(False)",
821 "enable_locking": "<bool> = Optional(False)",
821 "enable_locking": "<bool> = Optional(False)",
822 "enable_statistics": "<bool> = Optional(False)",
822 "enable_statistics": "<bool> = Optional(False)",
823 }
823 }
824
824
825 OUTPUT::
825 OUTPUT::
826
826
827 id : <id_given_in_input>
827 id : <id_given_in_input>
828 result: {
828 result: {
829 "msg": "Created new repository `<reponame>`",
829 "msg": "Created new repository `<reponame>`",
830 "repo": {
830 "repo": {
831 "repo_id" : "<repo_id>",
831 "repo_id" : "<repo_id>",
832 "repo_name" : "<reponame>"
832 "repo_name" : "<reponame>"
833 "repo_type" : "<repo_type>",
833 "repo_type" : "<repo_type>",
834 "clone_uri" : "<clone_uri>",
834 "clone_uri" : "<clone_uri>",
835 "private" : "<bool>",
835 "private" : "<bool>",
836 "created_on" : "<datetimecreated>",
836 "created_on" : "<datetimecreated>",
837 "description" : "<description>",
837 "description" : "<description>",
838 "landing_rev": "<landing_rev>",
838 "landing_rev": "<landing_rev>",
839 "owner": "<username or user_id>",
839 "owner": "<username or user_id>",
840 "fork_of": "<name_of_fork_parent>",
840 "fork_of": "<name_of_fork_parent>",
841 "enable_downloads": "<bool>",
841 "enable_downloads": "<bool>",
842 "enable_locking": "<bool>",
842 "enable_locking": "<bool>",
843 "enable_statistics": "<bool>",
843 "enable_statistics": "<bool>",
844 },
844 },
845 }
845 }
846 error: null
846 error: null
847
847
848 update_repo
848 update_repo
849 ^^^^^^^^^^^
849 ^^^^^^^^^^^
850
850
851 Update a repository.
851 Update a repository.
852 This command can only be executed using the api_key of a user with admin rights,
852 This command can only be executed using the api_key of a user with admin rights,
853 or that of a regular user with create repository permission.
853 or that of a regular user with create repository permission.
854 Regular users cannot specify owner parameter.
854 Regular users cannot specify owner parameter.
855
855
856 INPUT::
856 INPUT::
857
857
858 id : <id_for_response>
858 id : <id_for_response>
859 api_key : "<api_key>"
859 api_key : "<api_key>"
860 method : "update_repo"
860 method : "update_repo"
861 args: {
861 args: {
862 "repoid" : "<reponame or repo_id>"
862 "repoid" : "<reponame or repo_id>"
863 "name" : "<reponame> = Optional('')",
863 "name" : "<reponame> = Optional('')",
864 "group" : "<group_id> = Optional(None)",
864 "group" : "<group_id> = Optional(None)",
865 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
865 "owner" : "<owner_name_or_id = Optional(=apiuser)>",
866 "description" : "<description> = Optional('')",
866 "description" : "<description> = Optional('')",
867 "private" : "<bool> = Optional(False)",
867 "private" : "<bool> = Optional(False)",
868 "clone_uri" : "<clone_uri> = Optional(None)",
868 "clone_uri" : "<clone_uri> = Optional(None)",
869 "landing_rev" : "<landing_rev> = Optional('tip')",
869 "landing_rev" : "<landing_rev> = Optional('tip')",
870 "enable_downloads": "<bool> = Optional(False)",
870 "enable_downloads": "<bool> = Optional(False)",
871 "enable_locking": "<bool> = Optional(False)",
871 "enable_locking": "<bool> = Optional(False)",
872 "enable_statistics": "<bool> = Optional(False)",
872 "enable_statistics": "<bool> = Optional(False)",
873 }
873 }
874
874
875 OUTPUT::
875 OUTPUT::
876
876
877 id : <id_given_in_input>
877 id : <id_given_in_input>
878 result: {
878 result: {
879 "msg": "updated repo ID:repo_id `<reponame>`",
879 "msg": "updated repo ID:repo_id `<reponame>`",
880 "repository": {
880 "repository": {
881 "repo_id" : "<repo_id>",
881 "repo_id" : "<repo_id>",
882 "repo_name" : "<reponame>"
882 "repo_name" : "<reponame>"
883 "repo_type" : "<repo_type>",
883 "repo_type" : "<repo_type>",
884 "clone_uri" : "<clone_uri>",
884 "clone_uri" : "<clone_uri>",
885 "private": "<bool>",
885 "private": "<bool>",
886 "created_on" : "<datetimecreated>",
886 "created_on" : "<datetimecreated>",
887 "description" : "<description>",
887 "description" : "<description>",
888 "landing_rev": "<landing_rev>",
888 "landing_rev": "<landing_rev>",
889 "owner": "<username or user_id>",
889 "owner": "<username or user_id>",
890 "fork_of": "<name_of_fork_parent>",
890 "fork_of": "<name_of_fork_parent>",
891 "enable_downloads": "<bool>",
891 "enable_downloads": "<bool>",
892 "enable_locking": "<bool>",
892 "enable_locking": "<bool>",
893 "enable_statistics": "<bool>",
893 "enable_statistics": "<bool>",
894 "last_changeset": {
894 "last_changeset": {
895 "author": "<full_author>",
895 "author": "<full_author>",
896 "date": "<date_time_of_commit>",
896 "date": "<date_time_of_commit>",
897 "message": "<commit_message>",
897 "message": "<commit_message>",
898 "raw_id": "<raw_id>",
898 "raw_id": "<raw_id>",
899 "revision": "<numeric_revision>",
899 "revision": "<numeric_revision>",
900 "short_id": "<short_id>"
900 "short_id": "<short_id>"
901 }
901 }
902 "locked_by": "<username>",
902 "locked_by": "<username>",
903 "locked_date": "<float lock_time>",
903 "locked_date": "<float lock_time>",
904 },
904 },
905 }
905 }
906 error: null
906 error: null
907
907
908 fork_repo
908 fork_repo
909 ^^^^^^^^^
909 ^^^^^^^^^
910
910
911 Create a fork of the given repo. If using Celery, this will
911 Create a fork of the given repo. If using Celery, this will
912 return success message immediately and a fork will be created
912 return success message immediately and a fork will be created
913 asynchronously.
913 asynchronously.
914 This command can only be executed using the api_key of a user with admin
914 This command can only be executed using the api_key of a user with admin
915 rights, or with the global fork permission, by a regular user with create
915 rights, or with the global fork permission, by a regular user with create
916 repository permission and at least read access to the repository.
916 repository permission and at least read access to the repository.
917 Regular users cannot specify owner parameter.
917 Regular users cannot specify owner parameter.
918
918
919 INPUT::
919 INPUT::
920
920
921 id : <id_for_response>
921 id : <id_for_response>
922 api_key : "<api_key>"
922 api_key : "<api_key>"
923 method : "fork_repo"
923 method : "fork_repo"
924 args: {
924 args: {
925 "repoid" : "<reponame or repo_id>",
925 "repoid" : "<reponame or repo_id>",
926 "fork_name": "<forkname>",
926 "fork_name": "<forkname>",
927 "owner": "<username or user_id = Optional(=apiuser)>",
927 "owner": "<username or user_id = Optional(=apiuser)>",
928 "description": "<description>",
928 "description": "<description>",
929 "copy_permissions": "<bool>",
929 "copy_permissions": "<bool>",
930 "private": "<bool>",
930 "private": "<bool>",
931 "landing_rev": "<landing_rev>"
931 "landing_rev": "<landing_rev>"
932
932
933 }
933 }
934
934
935 OUTPUT::
935 OUTPUT::
936
936
937 id : <id_given_in_input>
937 id : <id_given_in_input>
938 result: {
938 result: {
939 "msg": "Created fork of `<reponame>` as `<forkname>`",
939 "msg": "Created fork of `<reponame>` as `<forkname>`",
940 "success": true
940 "success": true
941 }
941 }
942 error: null
942 error: null
943
943
944 delete_repo
944 delete_repo
945 ^^^^^^^^^^^
945 ^^^^^^^^^^^
946
946
947 Delete a repository.
947 Delete a repository.
948 This command can only be executed using the api_key of a user with admin rights,
948 This command can only be executed using the api_key of a user with admin rights,
949 or that of a regular user with admin access to the repository.
949 or that of a regular user with admin access to the repository.
950 When ``forks`` param is set it is possible to detach or delete forks of the deleted repository.
950 When ``forks`` param is set it is possible to detach or delete forks of the deleted repository.
951
951
952 INPUT::
952 INPUT::
953
953
954 id : <id_for_response>
954 id : <id_for_response>
955 api_key : "<api_key>"
955 api_key : "<api_key>"
956 method : "delete_repo"
956 method : "delete_repo"
957 args: {
957 args: {
958 "repoid" : "<reponame or repo_id>",
958 "repoid" : "<reponame or repo_id>",
959 "forks" : "`delete` or `detach` = Optional(None)"
959 "forks" : "`delete` or `detach` = Optional(None)"
960 }
960 }
961
961
962 OUTPUT::
962 OUTPUT::
963
963
964 id : <id_given_in_input>
964 id : <id_given_in_input>
965 result: {
965 result: {
966 "msg": "Deleted repository `<reponame>`",
966 "msg": "Deleted repository `<reponame>`",
967 "success": true
967 "success": true
968 }
968 }
969 error: null
969 error: null
970
970
971 grant_user_permission
971 grant_user_permission
972 ^^^^^^^^^^^^^^^^^^^^^
972 ^^^^^^^^^^^^^^^^^^^^^
973
973
974 Grant permission for a user on the given repository, or update the existing one if found.
974 Grant permission for a user on the given repository, or update the existing one if found.
975 This command can only be executed using the api_key of a user with admin rights.
975 This command can only be executed using the api_key of a user with admin rights.
976
976
977 INPUT::
977 INPUT::
978
978
979 id : <id_for_response>
979 id : <id_for_response>
980 api_key : "<api_key>"
980 api_key : "<api_key>"
981 method : "grant_user_permission"
981 method : "grant_user_permission"
982 args: {
982 args: {
983 "repoid" : "<reponame or repo_id>"
983 "repoid" : "<reponame or repo_id>"
984 "userid" : "<username or user_id>"
984 "userid" : "<username or user_id>"
985 "perm" : "(repository.(none|read|write|admin))",
985 "perm" : "(repository.(none|read|write|admin))",
986 }
986 }
987
987
988 OUTPUT::
988 OUTPUT::
989
989
990 id : <id_given_in_input>
990 id : <id_given_in_input>
991 result: {
991 result: {
992 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
992 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
993 "success": true
993 "success": true
994 }
994 }
995 error: null
995 error: null
996
996
997 revoke_user_permission
997 revoke_user_permission
998 ^^^^^^^^^^^^^^^^^^^^^^
998 ^^^^^^^^^^^^^^^^^^^^^^
999
999
1000 Revoke permission for a user on the given repository.
1000 Revoke permission for a user on the given repository.
1001 This command can only be executed using the api_key of a user with admin rights.
1001 This command can only be executed using the api_key of a user with admin rights.
1002
1002
1003 INPUT::
1003 INPUT::
1004
1004
1005 id : <id_for_response>
1005 id : <id_for_response>
1006 api_key : "<api_key>"
1006 api_key : "<api_key>"
1007 method : "revoke_user_permission"
1007 method : "revoke_user_permission"
1008 args: {
1008 args: {
1009 "repoid" : "<reponame or repo_id>"
1009 "repoid" : "<reponame or repo_id>"
1010 "userid" : "<username or user_id>"
1010 "userid" : "<username or user_id>"
1011 }
1011 }
1012
1012
1013 OUTPUT::
1013 OUTPUT::
1014
1014
1015 id : <id_given_in_input>
1015 id : <id_given_in_input>
1016 result: {
1016 result: {
1017 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1017 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1018 "success": true
1018 "success": true
1019 }
1019 }
1020 error: null
1020 error: null
1021
1021
1022 grant_user_group_permission
1022 grant_user_group_permission
1023 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1023 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1024
1024
1025 Grant permission for a user group on the given repository, or update the
1025 Grant permission for a user group on the given repository, or update the
1026 existing one if found.
1026 existing one if found.
1027 This command can only be executed using the api_key of a user with admin rights.
1027 This command can only be executed using the api_key of a user with admin rights.
1028
1028
1029 INPUT::
1029 INPUT::
1030
1030
1031 id : <id_for_response>
1031 id : <id_for_response>
1032 api_key : "<api_key>"
1032 api_key : "<api_key>"
1033 method : "grant_user_group_permission"
1033 method : "grant_user_group_permission"
1034 args: {
1034 args: {
1035 "repoid" : "<reponame or repo_id>"
1035 "repoid" : "<reponame or repo_id>"
1036 "usersgroupid" : "<user group id or name>"
1036 "usersgroupid" : "<user group id or name>"
1037 "perm" : "(repository.(none|read|write|admin))",
1037 "perm" : "(repository.(none|read|write|admin))",
1038 }
1038 }
1039
1039
1040 OUTPUT::
1040 OUTPUT::
1041
1041
1042 id : <id_given_in_input>
1042 id : <id_given_in_input>
1043 result: {
1043 result: {
1044 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1044 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1045 "success": true
1045 "success": true
1046 }
1046 }
1047 error: null
1047 error: null
1048
1048
1049 revoke_user_group_permission
1049 revoke_user_group_permission
1050 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1050 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1051
1051
1052 Revoke permission for a user group on the given repository.
1052 Revoke permission for a user group on the given repository.
1053 This command can only be executed using the api_key of a user with admin rights.
1053 This command can only be executed using the api_key of a user with admin rights.
1054
1054
1055 INPUT::
1055 INPUT::
1056
1056
1057 id : <id_for_response>
1057 id : <id_for_response>
1058 api_key : "<api_key>"
1058 api_key : "<api_key>"
1059 method : "revoke_user_group_permission"
1059 method : "revoke_user_group_permission"
1060 args: {
1060 args: {
1061 "repoid" : "<reponame or repo_id>"
1061 "repoid" : "<reponame or repo_id>"
1062 "usersgroupid" : "<user group id or name>"
1062 "usersgroupid" : "<user group id or name>"
1063 }
1063 }
1064
1064
1065 OUTPUT::
1065 OUTPUT::
1066
1066
1067 id : <id_given_in_input>
1067 id : <id_given_in_input>
1068 result: {
1068 result: {
1069 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1069 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1070 "success": true
1070 "success": true
1071 }
1071 }
1072 error: null
1072 error: null
1073
1073
1074 get_changesets
1075 ^^^^^^^^^^^^^^
1076
1077 Get changesets of a given repository. This command can only be executed using the api_key
1078 of a user with read permissions to the repository.
1079
1080 INPUT::
1081
1082 id : <id_for_response>
1083 api_key : "<api_key>"
1084 method : "get_changesets"
1085 args: {
1086 "repoid" : "<reponame or repo_id>",
1087 "start": "<revision number> = Optional(None)",
1088 "end": "<revision number> = Optional(None)",
1089 "start_date": "<date> = Optional(None)", # in "%Y-%m-%dT%H:%M:%S" format
1090 "end_date": "<date> = Optional(None)", # in "%Y-%m-%dT%H:%M:%S" format
1091 "branch_name": "<branch name filter> = Optional(None)",
1092 "reverse": "<bool> = Optional(False)",
1093 "with_file_list": "<bool> = Optional(False)"
1094 }
1095
1096 OUTPUT::
1097
1098 id : <id_given_in_input>
1099 result: [
1100 {
1101 "raw_id": "<raw_id>",
1102 "short_id": "short_id": "<short_id>",
1103 "author": "<full_author>",
1104 "date": "<date_time_of_commit>",
1105 "message": "<commit_message>",
1106 "revision": "<numeric_revision>",
1107 <if with_file_list == True>
1108 "added": [<list of added files>],
1109 "changed": [<list of changed files>],
1110 "removed": [<list of removed files>]
1111 },
1112 ...
1113 ]
1114 error: null
1115
1074 get_changeset
1116 get_changeset
1075 ^^^^^^^^^^^^^
1117 ^^^^^^^^^^^^^
1076
1118
1077 Get information and review status for a given changeset. This command can only
1119 Get information and review status for a given changeset. This command can only
1078 be executed using the api_key of a user with read permissions to the
1120 be executed using the api_key of a user with read permissions to the
1079 repository.
1121 repository.
1080
1122
1081 INPUT::
1123 INPUT::
1082
1124
1083 id : <id_for_response>
1125 id : <id_for_response>
1084 api_key : "<api_key>"
1126 api_key : "<api_key>"
1085 method : "get_changeset"
1127 method : "get_changeset"
1086 args: {
1128 args: {
1087 "repoid" : "<reponame or repo_id>",
1129 "repoid" : "<reponame or repo_id>",
1088 "raw_id" : "<raw_id>",
1130 "raw_id" : "<raw_id>",
1089 "with_reviews": "<bool> = Optional(False)"
1131 "with_reviews": "<bool> = Optional(False)"
1090 }
1132 }
1091
1133
1092 OUTPUT::
1134 OUTPUT::
1093
1135
1094 id : <id_given_in_input>
1136 id : <id_given_in_input>
1095 result: {
1137 result: {
1096 "author": "<full_author>",
1138 "author": "<full_author>",
1097 "date": "<date_time_of_commit>",
1139 "date": "<date_time_of_commit>",
1098 "message": "<commit_message>",
1140 "message": "<commit_message>",
1099 "raw_id": "<raw_id>",
1141 "raw_id": "<raw_id>",
1100 "revision": "<numeric_revision>",
1142 "revision": "<numeric_revision>",
1101 "short_id": "<short_id>",
1143 "short_id": "<short_id>",
1102 "reviews": [{
1144 "reviews": [{
1103 "reviewer": "<username>",
1145 "reviewer": "<username>",
1104 "modified_at": "<date_time_of_review>", # iso 8601 date, server's timezone
1146 "modified_at": "<date_time_of_review>", # iso 8601 date, server's timezone
1105 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
1147 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
1106 },
1148 },
1107 ...
1149 ...
1108 ]
1150 ]
1109 }
1151 }
1110 error: null
1152 error: null
1111
1153
1112 Example output::
1154 Example output::
1113
1155
1114 {
1156 {
1115 "id" : 1,
1157 "id" : 1,
1116 "error" : null,
1158 "error" : null,
1117 "result" : {
1159 "result" : {
1118 "author" : {
1160 "author" : {
1119 "email" : "user@example.com",
1161 "email" : "user@example.com",
1120 "name" : "Kallithea Admin"
1162 "name" : "Kallithea Admin"
1121 },
1163 },
1122 "changed" : [],
1164 "changed" : [],
1123 "short_id" : "e1022d3d28df",
1165 "short_id" : "e1022d3d28df",
1124 "date" : "2017-03-28T09:09:03",
1166 "date" : "2017-03-28T09:09:03",
1125 "added" : [
1167 "added" : [
1126 "README.rst"
1168 "README.rst"
1127 ],
1169 ],
1128 "removed" : [],
1170 "removed" : [],
1129 "revision" : 0,
1171 "revision" : 0,
1130 "raw_id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1172 "raw_id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1131 "message" : "Added file via Kallithea",
1173 "message" : "Added file via Kallithea",
1132 "id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1174 "id" : "e1022d3d28dfba02f626cde65dbe08f4ceb0e4e7",
1133 "reviews" : [
1175 "reviews" : [
1134 {
1176 {
1135 "status" : "under_review",
1177 "status" : "under_review",
1136 "modified_at" : "2017-03-28T09:17:08.618",
1178 "modified_at" : "2017-03-28T09:17:08.618",
1137 "reviewer" : "user"
1179 "reviewer" : "user"
1138 }
1180 }
1139 ]
1181 ]
1140 }
1182 }
1141 }
1183 }
1142
1184
1143 get_pullrequest
1185 get_pullrequest
1144 ^^^^^^^^^^^^^^^
1186 ^^^^^^^^^^^^^^^
1145
1187
1146 Get information and review status for a given pull request. This command can only be executed
1188 Get information and review status for a given pull request. This command can only be executed
1147 using the api_key of a user with read permissions to the original repository.
1189 using the api_key of a user with read permissions to the original repository.
1148
1190
1149 INPUT::
1191 INPUT::
1150
1192
1151 id : <id_for_response>
1193 id : <id_for_response>
1152 api_key : "<api_key>"
1194 api_key : "<api_key>"
1153 method : "get_pullrequest"
1195 method : "get_pullrequest"
1154 args: {
1196 args: {
1155 "pullrequest_id" : "<pullrequest_id>",
1197 "pullrequest_id" : "<pullrequest_id>",
1156 }
1198 }
1157
1199
1158 OUTPUT::
1200 OUTPUT::
1159
1201
1160 id : <id_given_in_input>
1202 id : <id_given_in_input>
1161 result: {
1203 result: {
1162 "status": "<pull_request_status>",
1204 "status": "<pull_request_status>",
1163 "pull_request_id": <pull_request_id>,
1205 "pull_request_id": <pull_request_id>,
1164 "description": "<pull_request_description>",
1206 "description": "<pull_request_description>",
1165 "title": "<pull_request_title>",
1207 "title": "<pull_request_title>",
1166 "url": "<pull_request_url>",
1208 "url": "<pull_request_url>",
1167 "reviewers": [
1209 "reviewers": [
1168 {
1210 {
1169 "username": "<user_name>",
1211 "username": "<user_name>",
1170 },
1212 },
1171 ...
1213 ...
1172 ],
1214 ],
1173 "org_repo_url": "<repo_url>",
1215 "org_repo_url": "<repo_url>",
1174 "org_ref_parts": [
1216 "org_ref_parts": [
1175 "<ref_type>",
1217 "<ref_type>",
1176 "<ref_name>",
1218 "<ref_name>",
1177 "<raw_id>"
1219 "<raw_id>"
1178 ],
1220 ],
1179 "other_ref_parts": [
1221 "other_ref_parts": [
1180 "<ref_type>",
1222 "<ref_type>",
1181 "<ref_name>",
1223 "<ref_name>",
1182 "<raw_id>"
1224 "<raw_id>"
1183 ],
1225 ],
1184 "comments": [
1226 "comments": [
1185 {
1227 {
1186 "username": "<user_name>",
1228 "username": "<user_name>",
1187 "text": "<comment text>",
1229 "text": "<comment text>",
1188 "comment_id": "<comment_id>",
1230 "comment_id": "<comment_id>",
1189 },
1231 },
1190 ...
1232 ...
1191 ],
1233 ],
1192 "owner": "<username>",
1234 "owner": "<username>",
1193 "statuses": [
1235 "statuses": [
1194 {
1236 {
1195 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
1237 "status": "<status_of_review>", # "under_review", "approved" or "rejected"
1196 "reviewer": "<user_name>",
1238 "reviewer": "<user_name>",
1197 "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
1239 "modified_at": "<date_time_of_review>" # iso 8601 date, server's timezone
1198 },
1240 },
1199 ...
1241 ...
1200 ],
1242 ],
1201 "revisions": [
1243 "revisions": [
1202 "<raw_id>",
1244 "<raw_id>",
1203 ...
1245 ...
1204 ]
1246 ]
1205 },
1247 },
1206 error: null
1248 error: null
1207
1249
1208 comment_pullrequest
1250 comment_pullrequest
1209 ^^^^^^^^^^^^^^^^^^^
1251 ^^^^^^^^^^^^^^^^^^^
1210
1252
1211 Add comment, change status or close a given pull request. This command can only be executed
1253 Add comment, change status or close a given pull request. This command can only be executed
1212 using the api_key of a user with read permissions to the original repository.
1254 using the api_key of a user with read permissions to the original repository.
1213
1255
1214 INPUT::
1256 INPUT::
1215
1257
1216 id : <id_for_response>
1258 id : <id_for_response>
1217 api_key : "<api_key>"
1259 api_key : "<api_key>"
1218 method : "comment_pullrequest"
1260 method : "comment_pullrequest"
1219 args: {
1261 args: {
1220 "pull_request_id": "<pull_request_id>",
1262 "pull_request_id": "<pull_request_id>",
1221 "comment_msg": Optional(''),
1263 "comment_msg": Optional(''),
1222 "status": Optional(None), # "under_review", "approved" or "rejected"
1264 "status": Optional(None), # "under_review", "approved" or "rejected"
1223 "close_pr": Optional(False)",
1265 "close_pr": Optional(False)",
1224 }
1266 }
1225
1267
1226 OUTPUT::
1268 OUTPUT::
1227
1269
1228 id : <id_given_in_input>
1270 id : <id_given_in_input>
1229 result: True
1271 result: True
1230 error: null
1272 error: null
1231
1273
1232
1274
1233 API access for web views
1275 API access for web views
1234 ------------------------
1276 ------------------------
1235
1277
1236 API access can also be turned on for each web view in Kallithea that is
1278 API access can also be turned on for each web view in Kallithea that is
1237 decorated with the ``@LoginRequired`` decorator. Some views use
1279 decorated with the ``@LoginRequired`` decorator. Some views use
1238 ``@LoginRequired(api_access=True)`` and are always available. By default only
1280 ``@LoginRequired(api_access=True)`` and are always available. By default only
1239 RSS/Atom feed views are enabled. Other views are
1281 RSS/Atom feed views are enabled. Other views are
1240 only available if they have been whitelisted. Edit the
1282 only available if they have been whitelisted. Edit the
1241 ``api_access_controllers_whitelist`` option in your .ini file and define views
1283 ``api_access_controllers_whitelist`` option in your .ini file and define views
1242 that should have API access enabled.
1284 that should have API access enabled.
1243
1285
1244 For example, to enable API access to patch/diff, raw file and archive::
1286 For example, to enable API access to patch/diff, raw file and archive::
1245
1287
1246 api_access_controllers_whitelist =
1288 api_access_controllers_whitelist =
1247 ChangesetController:changeset_patch,
1289 ChangesetController:changeset_patch,
1248 ChangesetController:changeset_raw,
1290 ChangesetController:changeset_raw,
1249 FilesController:raw,
1291 FilesController:raw,
1250 FilesController:archivefile
1292 FilesController:archivefile
1251
1293
1252 After this change, a Kallithea view can be accessed without login using
1294 After this change, a Kallithea view can be accessed without login using
1253 bearer authentication, by including this header with the request::
1295 bearer authentication, by including this header with the request::
1254
1296
1255 Authentication: Bearer <api_key>
1297 Authentication: Bearer <api_key>
1256
1298
1257 Alternatively, the API key can be passed in the URL query string using
1299 Alternatively, the API key can be passed in the URL query string using
1258 ``?api_key=<api_key>``, though this is not recommended due to the increased
1300 ``?api_key=<api_key>``, though this is not recommended due to the increased
1259 risk of API key leaks, and support will likely be removed in the future.
1301 risk of API key leaks, and support will likely be removed in the future.
1260
1302
1261 Exposing raw diffs is a good way to integrate with
1303 Exposing raw diffs is a good way to integrate with
1262 third-party services like code review, or build farms that can download archives.
1304 third-party services like code review, or build farms that can download archives.
@@ -1,2573 +1,2594 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 time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31
32 from datetime import datetime
31 from sqlalchemy import or_
33 from sqlalchemy import or_
32
34
33 from tg import request
35 from tg import request
34
36
35 from kallithea.controllers.api import JSONRPCController, JSONRPCError
37 from kallithea.controllers.api import JSONRPCController, JSONRPCError
36 from kallithea.lib.auth import (
38 from kallithea.lib.auth import (
37 PasswordGenerator, AuthUser, HasPermissionAnyDecorator,
39 PasswordGenerator, AuthUser, HasPermissionAnyDecorator,
38 HasPermissionAnyDecorator, HasPermissionAny, HasRepoPermissionLevel,
40 HasPermissionAnyDecorator, HasPermissionAny, HasRepoPermissionLevel,
39 HasRepoGroupPermissionLevel, HasUserGroupPermissionLevel)
41 HasRepoGroupPermissionLevel, HasUserGroupPermissionLevel)
40 from kallithea.lib.utils import map_groups, repo2db_mapper
42 from kallithea.lib.utils import map_groups, repo2db_mapper
41 from kallithea.lib.utils2 import (
43 from kallithea.lib.utils2 import (
42 str2bool, time_to_datetime, safe_int, Optional, OAttr)
44 str2bool, time_to_datetime, safe_int, Optional, OAttr)
43 from kallithea.model.meta import Session
45 from kallithea.model.meta import Session
44 from kallithea.model.repo_group import RepoGroupModel
46 from kallithea.model.repo_group import RepoGroupModel
45 from kallithea.model.scm import ScmModel, UserGroupList
47 from kallithea.model.scm import ScmModel, UserGroupList
46 from kallithea.model.repo import RepoModel
48 from kallithea.model.repo import RepoModel
47 from kallithea.model.user import UserModel
49 from kallithea.model.user import UserModel
48 from kallithea.model.user_group import UserGroupModel
50 from kallithea.model.user_group import UserGroupModel
49 from kallithea.model.gist import GistModel
51 from kallithea.model.gist import GistModel
50 from kallithea.model.changeset_status import ChangesetStatusModel
52 from kallithea.model.changeset_status import ChangesetStatusModel
51 from kallithea.model.comment import ChangesetCommentsModel
53 from kallithea.model.comment import ChangesetCommentsModel
52 from kallithea.model.pull_request import PullRequestModel
54 from kallithea.model.pull_request import PullRequestModel
53 from kallithea.model.db import (
55 from kallithea.model.db import (
54 Repository, Setting, UserIpMap, Permission, User, Gist,
56 Repository, Setting, UserIpMap, Permission, User, Gist,
55 RepoGroup, UserGroup, PullRequest, ChangesetStatus)
57 RepoGroup, UserGroup, PullRequest, ChangesetStatus)
56 from kallithea.lib.compat import json
58 from kallithea.lib.compat import json
57 from kallithea.lib.exceptions import (
59 from kallithea.lib.exceptions import (
58 DefaultUserException, UserGroupsAssignedException)
60 DefaultUserException, UserGroupsAssignedException)
59 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError
61 from kallithea.lib.vcs.exceptions import ChangesetDoesNotExistError, EmptyRepositoryError
60 from kallithea.lib.vcs.backends.base import EmptyChangeset
62 from kallithea.lib.vcs.backends.base import EmptyChangeset
61 from kallithea.lib.utils import action_logger
63 from kallithea.lib.utils import action_logger
62
64
63 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
64
66
65
67
66 def store_update(updates, attr, name):
68 def store_update(updates, attr, name):
67 """
69 """
68 Stores param in updates dict if it's not instance of Optional
70 Stores param in updates dict if it's not instance of Optional
69 allows easy updates of passed in params
71 allows easy updates of passed in params
70 """
72 """
71 if not isinstance(attr, Optional):
73 if not isinstance(attr, Optional):
72 updates[name] = attr
74 updates[name] = attr
73
75
74
76
75 def get_user_or_error(userid):
77 def get_user_or_error(userid):
76 """
78 """
77 Get user by id or name or return JsonRPCError if not found
79 Get user by id or name or return JsonRPCError if not found
78
80
79 :param userid:
81 :param userid:
80 """
82 """
81 user = UserModel().get_user(userid)
83 user = UserModel().get_user(userid)
82 if user is None:
84 if user is None:
83 raise JSONRPCError("user `%s` does not exist" % (userid,))
85 raise JSONRPCError("user `%s` does not exist" % (userid,))
84 return user
86 return user
85
87
86
88
87 def get_repo_or_error(repoid):
89 def get_repo_or_error(repoid):
88 """
90 """
89 Get repo by id or name or return JsonRPCError if not found
91 Get repo by id or name or return JsonRPCError if not found
90
92
91 :param repoid:
93 :param repoid:
92 """
94 """
93 repo = RepoModel().get_repo(repoid)
95 repo = RepoModel().get_repo(repoid)
94 if repo is None:
96 if repo is None:
95 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
97 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
96 return repo
98 return repo
97
99
98
100
99 def get_repo_group_or_error(repogroupid):
101 def get_repo_group_or_error(repogroupid):
100 """
102 """
101 Get repo group by id or name or return JsonRPCError if not found
103 Get repo group by id or name or return JsonRPCError if not found
102
104
103 :param repogroupid:
105 :param repogroupid:
104 """
106 """
105 repo_group = RepoGroup.guess_instance(repogroupid)
107 repo_group = RepoGroup.guess_instance(repogroupid)
106 if repo_group is None:
108 if repo_group is None:
107 raise JSONRPCError(
109 raise JSONRPCError(
108 'repository group `%s` does not exist' % (repogroupid,))
110 'repository group `%s` does not exist' % (repogroupid,))
109 return repo_group
111 return repo_group
110
112
111
113
112 def get_user_group_or_error(usergroupid):
114 def get_user_group_or_error(usergroupid):
113 """
115 """
114 Get user group by id or name or return JsonRPCError if not found
116 Get user group by id or name or return JsonRPCError if not found
115
117
116 :param usergroupid:
118 :param usergroupid:
117 """
119 """
118 user_group = UserGroupModel().get_group(usergroupid)
120 user_group = UserGroupModel().get_group(usergroupid)
119 if user_group is None:
121 if user_group is None:
120 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
122 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
121 return user_group
123 return user_group
122
124
123
125
124 def get_perm_or_error(permid, prefix=None):
126 def get_perm_or_error(permid, prefix=None):
125 """
127 """
126 Get permission by id or name or return JsonRPCError if not found
128 Get permission by id or name or return JsonRPCError if not found
127
129
128 :param permid:
130 :param permid:
129 """
131 """
130 perm = Permission.get_by_key(permid)
132 perm = Permission.get_by_key(permid)
131 if perm is None:
133 if perm is None:
132 raise JSONRPCError('permission `%s` does not exist' % (permid,))
134 raise JSONRPCError('permission `%s` does not exist' % (permid,))
133 if prefix:
135 if prefix:
134 if not perm.permission_name.startswith(prefix):
136 if not perm.permission_name.startswith(prefix):
135 raise JSONRPCError('permission `%s` is invalid, '
137 raise JSONRPCError('permission `%s` is invalid, '
136 'should start with %s' % (permid, prefix))
138 'should start with %s' % (permid, prefix))
137 return perm
139 return perm
138
140
139
141
140 def get_gist_or_error(gistid):
142 def get_gist_or_error(gistid):
141 """
143 """
142 Get gist by id or gist_access_id or return JsonRPCError if not found
144 Get gist by id or gist_access_id or return JsonRPCError if not found
143
145
144 :param gistid:
146 :param gistid:
145 """
147 """
146 gist = GistModel().get_gist(gistid)
148 gist = GistModel().get_gist(gistid)
147 if gist is None:
149 if gist is None:
148 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
150 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
149 return gist
151 return gist
150
152
151
153
152 class ApiController(JSONRPCController):
154 class ApiController(JSONRPCController):
153 """
155 """
154 API Controller
156 API Controller
155
157
156 The authenticated user can be found as request.authuser.
158 The authenticated user can be found as request.authuser.
157
159
158 Example function::
160 Example function::
159
161
160 def func(arg1, arg2,...):
162 def func(arg1, arg2,...):
161 pass
163 pass
162
164
163 Each function should also **raise** JSONRPCError for any
165 Each function should also **raise** JSONRPCError for any
164 errors that happens.
166 errors that happens.
165 """
167 """
166
168
167 @HasPermissionAnyDecorator('hg.admin')
169 @HasPermissionAnyDecorator('hg.admin')
168 def test(self, args):
170 def test(self, args):
169 return args
171 return args
170
172
171 @HasPermissionAnyDecorator('hg.admin')
173 @HasPermissionAnyDecorator('hg.admin')
172 def pull(self, repoid):
174 def pull(self, repoid):
173 """
175 """
174 Triggers a pull from remote location on given repo. Can be used to
176 Triggers a pull from remote location on given repo. Can be used to
175 automatically keep remote repos up to date. This command can be executed
177 automatically keep remote repos up to date. This command can be executed
176 only using api_key belonging to user with admin rights
178 only using api_key belonging to user with admin rights
177
179
178 :param repoid: repository name or repository id
180 :param repoid: repository name or repository id
179 :type repoid: str or int
181 :type repoid: str or int
180
182
181 OUTPUT::
183 OUTPUT::
182
184
183 id : <id_given_in_input>
185 id : <id_given_in_input>
184 result : {
186 result : {
185 "msg": "Pulled from `<repository name>`"
187 "msg": "Pulled from `<repository name>`"
186 "repository": "<repository name>"
188 "repository": "<repository name>"
187 }
189 }
188 error : null
190 error : null
189
191
190 ERROR OUTPUT::
192 ERROR OUTPUT::
191
193
192 id : <id_given_in_input>
194 id : <id_given_in_input>
193 result : null
195 result : null
194 error : {
196 error : {
195 "Unable to pull changes from `<reponame>`"
197 "Unable to pull changes from `<reponame>`"
196 }
198 }
197
199
198 """
200 """
199
201
200 repo = get_repo_or_error(repoid)
202 repo = get_repo_or_error(repoid)
201
203
202 try:
204 try:
203 ScmModel().pull_changes(repo.repo_name,
205 ScmModel().pull_changes(repo.repo_name,
204 request.authuser.username)
206 request.authuser.username)
205 return dict(
207 return dict(
206 msg='Pulled from `%s`' % repo.repo_name,
208 msg='Pulled from `%s`' % repo.repo_name,
207 repository=repo.repo_name
209 repository=repo.repo_name
208 )
210 )
209 except Exception:
211 except Exception:
210 log.error(traceback.format_exc())
212 log.error(traceback.format_exc())
211 raise JSONRPCError(
213 raise JSONRPCError(
212 'Unable to pull changes from `%s`' % repo.repo_name
214 'Unable to pull changes from `%s`' % repo.repo_name
213 )
215 )
214
216
215 @HasPermissionAnyDecorator('hg.admin')
217 @HasPermissionAnyDecorator('hg.admin')
216 def rescan_repos(self, remove_obsolete=Optional(False)):
218 def rescan_repos(self, remove_obsolete=Optional(False)):
217 """
219 """
218 Triggers rescan repositories action. If remove_obsolete is set
220 Triggers rescan repositories action. If remove_obsolete is set
219 than also delete repos that are in database but not in the filesystem.
221 than also delete repos that are in database but not in the filesystem.
220 aka "clean zombies". This command can be executed only using api_key
222 aka "clean zombies". This command can be executed only using api_key
221 belonging to user with admin rights.
223 belonging to user with admin rights.
222
224
223 :param remove_obsolete: deletes repositories from
225 :param remove_obsolete: deletes repositories from
224 database that are not found on the filesystem
226 database that are not found on the filesystem
225 :type remove_obsolete: Optional(bool)
227 :type remove_obsolete: Optional(bool)
226
228
227 OUTPUT::
229 OUTPUT::
228
230
229 id : <id_given_in_input>
231 id : <id_given_in_input>
230 result : {
232 result : {
231 'added': [<added repository name>,...]
233 'added': [<added repository name>,...]
232 'removed': [<removed repository name>,...]
234 'removed': [<removed repository name>,...]
233 }
235 }
234 error : null
236 error : null
235
237
236 ERROR OUTPUT::
238 ERROR OUTPUT::
237
239
238 id : <id_given_in_input>
240 id : <id_given_in_input>
239 result : null
241 result : null
240 error : {
242 error : {
241 'Error occurred during rescan repositories action'
243 'Error occurred during rescan repositories action'
242 }
244 }
243
245
244 """
246 """
245
247
246 try:
248 try:
247 rm_obsolete = Optional.extract(remove_obsolete)
249 rm_obsolete = Optional.extract(remove_obsolete)
248 added, removed = repo2db_mapper(ScmModel().repo_scan(),
250 added, removed = repo2db_mapper(ScmModel().repo_scan(),
249 remove_obsolete=rm_obsolete)
251 remove_obsolete=rm_obsolete)
250 return {'added': added, 'removed': removed}
252 return {'added': added, 'removed': removed}
251 except Exception:
253 except Exception:
252 log.error(traceback.format_exc())
254 log.error(traceback.format_exc())
253 raise JSONRPCError(
255 raise JSONRPCError(
254 'Error occurred during rescan repositories action'
256 'Error occurred during rescan repositories action'
255 )
257 )
256
258
257 def invalidate_cache(self, repoid):
259 def invalidate_cache(self, repoid):
258 """
260 """
259 Invalidate cache for repository.
261 Invalidate cache for repository.
260 This command can be executed only using api_key belonging to user with admin
262 This command can be executed only using api_key belonging to user with admin
261 rights or regular user that have write or admin or write access to repository.
263 rights or regular user that have write or admin or write access to repository.
262
264
263 :param repoid: repository name or repository id
265 :param repoid: repository name or repository id
264 :type repoid: str or int
266 :type repoid: str or int
265
267
266 OUTPUT::
268 OUTPUT::
267
269
268 id : <id_given_in_input>
270 id : <id_given_in_input>
269 result : {
271 result : {
270 'msg': Cache for repository `<repository name>` was invalidated,
272 'msg': Cache for repository `<repository name>` was invalidated,
271 'repository': <repository name>
273 'repository': <repository name>
272 }
274 }
273 error : null
275 error : null
274
276
275 ERROR OUTPUT::
277 ERROR OUTPUT::
276
278
277 id : <id_given_in_input>
279 id : <id_given_in_input>
278 result : null
280 result : null
279 error : {
281 error : {
280 'Error occurred during cache invalidation action'
282 'Error occurred during cache invalidation action'
281 }
283 }
282
284
283 """
285 """
284 repo = get_repo_or_error(repoid)
286 repo = get_repo_or_error(repoid)
285 if not HasPermissionAny('hg.admin')():
287 if not HasPermissionAny('hg.admin')():
286 if not HasRepoPermissionLevel('write')(repo.repo_name):
288 if not HasRepoPermissionLevel('write')(repo.repo_name):
287 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
289 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
288
290
289 try:
291 try:
290 ScmModel().mark_for_invalidation(repo.repo_name)
292 ScmModel().mark_for_invalidation(repo.repo_name)
291 return dict(
293 return dict(
292 msg='Cache for repository `%s` was invalidated' % (repoid,),
294 msg='Cache for repository `%s` was invalidated' % (repoid,),
293 repository=repo.repo_name
295 repository=repo.repo_name
294 )
296 )
295 except Exception:
297 except Exception:
296 log.error(traceback.format_exc())
298 log.error(traceback.format_exc())
297 raise JSONRPCError(
299 raise JSONRPCError(
298 'Error occurred during cache invalidation action'
300 'Error occurred during cache invalidation action'
299 )
301 )
300
302
301 # permission check inside
303 # permission check inside
302 def lock(self, repoid, locked=Optional(None),
304 def lock(self, repoid, locked=Optional(None),
303 userid=Optional(OAttr('apiuser'))):
305 userid=Optional(OAttr('apiuser'))):
304 """
306 """
305 Set locking state on given repository by given user. If userid param
307 Set locking state on given repository by given user. If userid param
306 is skipped, then it is set to id of user who is calling this method.
308 is skipped, then it is set to id of user who is calling this method.
307 If locked param is skipped then function shows current lock state of
309 If locked param is skipped then function shows current lock state of
308 given repo. This command can be executed only using api_key belonging
310 given repo. This command can be executed only using api_key belonging
309 to user with admin rights or regular user that have admin or write
311 to user with admin rights or regular user that have admin or write
310 access to repository.
312 access to repository.
311
313
312 :param repoid: repository name or repository id
314 :param repoid: repository name or repository id
313 :type repoid: str or int
315 :type repoid: str or int
314 :param locked: lock state to be set
316 :param locked: lock state to be set
315 :type locked: Optional(bool)
317 :type locked: Optional(bool)
316 :param userid: set lock as user
318 :param userid: set lock as user
317 :type userid: Optional(str or int)
319 :type userid: Optional(str or int)
318
320
319 OUTPUT::
321 OUTPUT::
320
322
321 id : <id_given_in_input>
323 id : <id_given_in_input>
322 result : {
324 result : {
323 'repo': '<reponame>',
325 'repo': '<reponame>',
324 'locked': <bool: lock state>,
326 'locked': <bool: lock state>,
325 'locked_since': <int: lock timestamp>,
327 'locked_since': <int: lock timestamp>,
326 'locked_by': <username of person who made the lock>,
328 'locked_by': <username of person who made the lock>,
327 'lock_state_changed': <bool: True if lock state has been changed in this request>,
329 'lock_state_changed': <bool: True if lock state has been changed in this request>,
328 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
330 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
329 or
331 or
330 'msg': 'Repo `<repository name>` not locked.'
332 'msg': 'Repo `<repository name>` not locked.'
331 or
333 or
332 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
334 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
333 }
335 }
334 error : null
336 error : null
335
337
336 ERROR OUTPUT::
338 ERROR OUTPUT::
337
339
338 id : <id_given_in_input>
340 id : <id_given_in_input>
339 result : null
341 result : null
340 error : {
342 error : {
341 'Error occurred locking repository `<reponame>`
343 'Error occurred locking repository `<reponame>`
342 }
344 }
343
345
344 """
346 """
345 repo = get_repo_or_error(repoid)
347 repo = get_repo_or_error(repoid)
346 if HasPermissionAny('hg.admin')():
348 if HasPermissionAny('hg.admin')():
347 pass
349 pass
348 elif HasRepoPermissionLevel('write')(repo.repo_name):
350 elif HasRepoPermissionLevel('write')(repo.repo_name):
349 # make sure normal user does not pass someone else userid,
351 # make sure normal user does not pass someone else userid,
350 # he is not allowed to do that
352 # he is not allowed to do that
351 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
353 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
352 raise JSONRPCError(
354 raise JSONRPCError(
353 'userid is not the same as your user'
355 'userid is not the same as your user'
354 )
356 )
355 else:
357 else:
356 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
358 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
357
359
358 if isinstance(userid, Optional):
360 if isinstance(userid, Optional):
359 userid = request.authuser.user_id
361 userid = request.authuser.user_id
360
362
361 user = get_user_or_error(userid)
363 user = get_user_or_error(userid)
362
364
363 if isinstance(locked, Optional):
365 if isinstance(locked, Optional):
364 lockobj = Repository.getlock(repo)
366 lockobj = Repository.getlock(repo)
365
367
366 if lockobj[0] is None:
368 if lockobj[0] is None:
367 _d = {
369 _d = {
368 'repo': repo.repo_name,
370 'repo': repo.repo_name,
369 'locked': False,
371 'locked': False,
370 'locked_since': None,
372 'locked_since': None,
371 'locked_by': None,
373 'locked_by': None,
372 'lock_state_changed': False,
374 'lock_state_changed': False,
373 'msg': 'Repo `%s` not locked.' % repo.repo_name
375 'msg': 'Repo `%s` not locked.' % repo.repo_name
374 }
376 }
375 return _d
377 return _d
376 else:
378 else:
377 userid, time_ = lockobj
379 userid, time_ = lockobj
378 lock_user = get_user_or_error(userid)
380 lock_user = get_user_or_error(userid)
379 _d = {
381 _d = {
380 'repo': repo.repo_name,
382 'repo': repo.repo_name,
381 'locked': True,
383 'locked': True,
382 'locked_since': time_,
384 'locked_since': time_,
383 'locked_by': lock_user.username,
385 'locked_by': lock_user.username,
384 'lock_state_changed': False,
386 'lock_state_changed': False,
385 'msg': ('Repo `%s` locked by `%s` on `%s`.'
387 'msg': ('Repo `%s` locked by `%s` on `%s`.'
386 % (repo.repo_name, lock_user.username,
388 % (repo.repo_name, lock_user.username,
387 json.dumps(time_to_datetime(time_))))
389 json.dumps(time_to_datetime(time_))))
388 }
390 }
389 return _d
391 return _d
390
392
391 # force locked state through a flag
393 # force locked state through a flag
392 else:
394 else:
393 locked = str2bool(locked)
395 locked = str2bool(locked)
394 try:
396 try:
395 if locked:
397 if locked:
396 lock_time = time.time()
398 lock_time = time.time()
397 Repository.lock(repo, user.user_id, lock_time)
399 Repository.lock(repo, user.user_id, lock_time)
398 else:
400 else:
399 lock_time = None
401 lock_time = None
400 Repository.unlock(repo)
402 Repository.unlock(repo)
401 _d = {
403 _d = {
402 'repo': repo.repo_name,
404 'repo': repo.repo_name,
403 'locked': locked,
405 'locked': locked,
404 'locked_since': lock_time,
406 'locked_since': lock_time,
405 'locked_by': user.username,
407 'locked_by': user.username,
406 'lock_state_changed': True,
408 'lock_state_changed': True,
407 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
409 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
408 % (user.username, repo.repo_name, locked))
410 % (user.username, repo.repo_name, locked))
409 }
411 }
410 return _d
412 return _d
411 except Exception:
413 except Exception:
412 log.error(traceback.format_exc())
414 log.error(traceback.format_exc())
413 raise JSONRPCError(
415 raise JSONRPCError(
414 'Error occurred locking repository `%s`' % repo.repo_name
416 'Error occurred locking repository `%s`' % repo.repo_name
415 )
417 )
416
418
417 def get_locks(self, userid=Optional(OAttr('apiuser'))):
419 def get_locks(self, userid=Optional(OAttr('apiuser'))):
418 """
420 """
419 Get all repositories with locks for given userid, if
421 Get all repositories with locks for given userid, if
420 this command is run by non-admin account userid is set to user
422 this command is run by non-admin account userid is set to user
421 who is calling this method, thus returning locks for himself.
423 who is calling this method, thus returning locks for himself.
422
424
423 :param userid: User to get locks for
425 :param userid: User to get locks for
424 :type userid: Optional(str or int)
426 :type userid: Optional(str or int)
425
427
426 OUTPUT::
428 OUTPUT::
427
429
428 id : <id_given_in_input>
430 id : <id_given_in_input>
429 result : {
431 result : {
430 [repo_object, repo_object,...]
432 [repo_object, repo_object,...]
431 }
433 }
432 error : null
434 error : null
433 """
435 """
434
436
435 if not HasPermissionAny('hg.admin')():
437 if not HasPermissionAny('hg.admin')():
436 # make sure normal user does not pass someone else userid,
438 # make sure normal user does not pass someone else userid,
437 # he is not allowed to do that
439 # he is not allowed to do that
438 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
440 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
439 raise JSONRPCError(
441 raise JSONRPCError(
440 'userid is not the same as your user'
442 'userid is not the same as your user'
441 )
443 )
442
444
443 ret = []
445 ret = []
444 if isinstance(userid, Optional):
446 if isinstance(userid, Optional):
445 user = None
447 user = None
446 else:
448 else:
447 user = get_user_or_error(userid)
449 user = get_user_or_error(userid)
448
450
449 # show all locks
451 # show all locks
450 for r in Repository.query():
452 for r in Repository.query():
451 userid, time_ = r.locked
453 userid, time_ = r.locked
452 if time_:
454 if time_:
453 _api_data = r.get_api_data()
455 _api_data = r.get_api_data()
454 # if we use userfilter just show the locks for this user
456 # if we use userfilter just show the locks for this user
455 if user is not None:
457 if user is not None:
456 if safe_int(userid) == user.user_id:
458 if safe_int(userid) == user.user_id:
457 ret.append(_api_data)
459 ret.append(_api_data)
458 else:
460 else:
459 ret.append(_api_data)
461 ret.append(_api_data)
460
462
461 return ret
463 return ret
462
464
463 @HasPermissionAnyDecorator('hg.admin')
465 @HasPermissionAnyDecorator('hg.admin')
464 def get_ip(self, userid=Optional(OAttr('apiuser'))):
466 def get_ip(self, userid=Optional(OAttr('apiuser'))):
465 """
467 """
466 Shows IP address as seen from Kallithea server, together with all
468 Shows IP address as seen from Kallithea server, together with all
467 defined IP addresses for given user. If userid is not passed data is
469 defined IP addresses for given user. If userid is not passed data is
468 returned for user who's calling this function.
470 returned for user who's calling this function.
469 This command can be executed only using api_key belonging to user with
471 This command can be executed only using api_key belonging to user with
470 admin rights.
472 admin rights.
471
473
472 :param userid: username to show ips for
474 :param userid: username to show ips for
473 :type userid: Optional(str or int)
475 :type userid: Optional(str or int)
474
476
475 OUTPUT::
477 OUTPUT::
476
478
477 id : <id_given_in_input>
479 id : <id_given_in_input>
478 result : {
480 result : {
479 "server_ip_addr": "<ip_from_clien>",
481 "server_ip_addr": "<ip_from_clien>",
480 "user_ips": [
482 "user_ips": [
481 {
483 {
482 "ip_addr": "<ip_with_mask>",
484 "ip_addr": "<ip_with_mask>",
483 "ip_range": ["<start_ip>", "<end_ip>"],
485 "ip_range": ["<start_ip>", "<end_ip>"],
484 },
486 },
485 ...
487 ...
486 ]
488 ]
487 }
489 }
488
490
489 """
491 """
490 if isinstance(userid, Optional):
492 if isinstance(userid, Optional):
491 userid = request.authuser.user_id
493 userid = request.authuser.user_id
492 user = get_user_or_error(userid)
494 user = get_user_or_error(userid)
493 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
495 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
494 return dict(
496 return dict(
495 server_ip_addr=request.ip_addr,
497 server_ip_addr=request.ip_addr,
496 user_ips=ips
498 user_ips=ips
497 )
499 )
498
500
499 # alias for old
501 # alias for old
500 show_ip = get_ip
502 show_ip = get_ip
501
503
502 @HasPermissionAnyDecorator('hg.admin')
504 @HasPermissionAnyDecorator('hg.admin')
503 def get_server_info(self):
505 def get_server_info(self):
504 """
506 """
505 return server info, including Kallithea version and installed packages
507 return server info, including Kallithea version and installed packages
506
508
507
509
508 OUTPUT::
510 OUTPUT::
509
511
510 id : <id_given_in_input>
512 id : <id_given_in_input>
511 result : {
513 result : {
512 'modules': [<module name>,...]
514 'modules': [<module name>,...]
513 'py_version': <python version>,
515 'py_version': <python version>,
514 'platform': <platform type>,
516 'platform': <platform type>,
515 'kallithea_version': <kallithea version>
517 'kallithea_version': <kallithea version>
516 }
518 }
517 error : null
519 error : null
518 """
520 """
519 return Setting.get_server_info()
521 return Setting.get_server_info()
520
522
521 def get_user(self, userid=Optional(OAttr('apiuser'))):
523 def get_user(self, userid=Optional(OAttr('apiuser'))):
522 """
524 """
523 Gets a user by username or user_id, Returns empty result if user is
525 Gets a user by username or user_id, Returns empty result if user is
524 not found. If userid param is skipped it is set to id of user who is
526 not found. If userid param is skipped it is set to id of user who is
525 calling this method. This command can be executed only using api_key
527 calling this method. This command can be executed only using api_key
526 belonging to user with admin rights, or regular users that cannot
528 belonging to user with admin rights, or regular users that cannot
527 specify different userid than theirs
529 specify different userid than theirs
528
530
529 :param userid: user to get data for
531 :param userid: user to get data for
530 :type userid: Optional(str or int)
532 :type userid: Optional(str or int)
531
533
532 OUTPUT::
534 OUTPUT::
533
535
534 id : <id_given_in_input>
536 id : <id_given_in_input>
535 result: None if user does not exist or
537 result: None if user does not exist or
536 {
538 {
537 "user_id" : "<user_id>",
539 "user_id" : "<user_id>",
538 "api_key" : "<api_key>",
540 "api_key" : "<api_key>",
539 "api_keys": "[<list of all API keys including additional ones>]"
541 "api_keys": "[<list of all API keys including additional ones>]"
540 "username" : "<username>",
542 "username" : "<username>",
541 "firstname": "<firstname>",
543 "firstname": "<firstname>",
542 "lastname" : "<lastname>",
544 "lastname" : "<lastname>",
543 "email" : "<email>",
545 "email" : "<email>",
544 "emails": "[<list of all emails including additional ones>]",
546 "emails": "[<list of all emails including additional ones>]",
545 "ip_addresses": "[<ip_address_for_user>,...]",
547 "ip_addresses": "[<ip_address_for_user>,...]",
546 "active" : "<bool: user active>",
548 "active" : "<bool: user active>",
547 "admin" :Β  "<bool: user is admin>",
549 "admin" :Β  "<bool: user is admin>",
548 "extern_name" : "<extern_name>",
550 "extern_name" : "<extern_name>",
549 "extern_type" : "<extern type>
551 "extern_type" : "<extern type>
550 "last_login": "<last_login>",
552 "last_login": "<last_login>",
551 "permissions": {
553 "permissions": {
552 "global": ["hg.create.repository",
554 "global": ["hg.create.repository",
553 "repository.read",
555 "repository.read",
554 "hg.register.manual_activate"],
556 "hg.register.manual_activate"],
555 "repositories": {"repo1": "repository.none"},
557 "repositories": {"repo1": "repository.none"},
556 "repositories_groups": {"Group1": "group.read"}
558 "repositories_groups": {"Group1": "group.read"}
557 },
559 },
558 }
560 }
559
561
560 error: null
562 error: null
561
563
562 """
564 """
563 if not HasPermissionAny('hg.admin')():
565 if not HasPermissionAny('hg.admin')():
564 # make sure normal user does not pass someone else userid,
566 # make sure normal user does not pass someone else userid,
565 # he is not allowed to do that
567 # he is not allowed to do that
566 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
568 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
567 raise JSONRPCError(
569 raise JSONRPCError(
568 'userid is not the same as your user'
570 'userid is not the same as your user'
569 )
571 )
570
572
571 if isinstance(userid, Optional):
573 if isinstance(userid, Optional):
572 userid = request.authuser.user_id
574 userid = request.authuser.user_id
573
575
574 user = get_user_or_error(userid)
576 user = get_user_or_error(userid)
575 data = user.get_api_data()
577 data = user.get_api_data()
576 data['permissions'] = AuthUser(user_id=user.user_id).permissions
578 data['permissions'] = AuthUser(user_id=user.user_id).permissions
577 return data
579 return data
578
580
579 @HasPermissionAnyDecorator('hg.admin')
581 @HasPermissionAnyDecorator('hg.admin')
580 def get_users(self):
582 def get_users(self):
581 """
583 """
582 Lists all existing users. This command can be executed only using api_key
584 Lists all existing users. This command can be executed only using api_key
583 belonging to user with admin rights.
585 belonging to user with admin rights.
584
586
585
587
586 OUTPUT::
588 OUTPUT::
587
589
588 id : <id_given_in_input>
590 id : <id_given_in_input>
589 result: [<user_object>, ...]
591 result: [<user_object>, ...]
590 error: null
592 error: null
591 """
593 """
592
594
593 return [
595 return [
594 user.get_api_data()
596 user.get_api_data()
595 for user in User.query()
597 for user in User.query()
596 .order_by(User.username)
598 .order_by(User.username)
597 .filter_by(is_default_user=False)
599 .filter_by(is_default_user=False)
598 ]
600 ]
599
601
600 @HasPermissionAnyDecorator('hg.admin')
602 @HasPermissionAnyDecorator('hg.admin')
601 def create_user(self, username, email, password=Optional(''),
603 def create_user(self, username, email, password=Optional(''),
602 firstname=Optional(u''), lastname=Optional(u''),
604 firstname=Optional(u''), lastname=Optional(u''),
603 active=Optional(True), admin=Optional(False),
605 active=Optional(True), admin=Optional(False),
604 extern_type=Optional(User.DEFAULT_AUTH_TYPE),
606 extern_type=Optional(User.DEFAULT_AUTH_TYPE),
605 extern_name=Optional('')):
607 extern_name=Optional('')):
606 """
608 """
607 Creates new user. Returns new user object. This command can
609 Creates new user. Returns new user object. This command can
608 be executed only using api_key belonging to user with admin rights.
610 be executed only using api_key belonging to user with admin rights.
609
611
610 :param username: new username
612 :param username: new username
611 :type username: str or int
613 :type username: str or int
612 :param email: email
614 :param email: email
613 :type email: str
615 :type email: str
614 :param password: password
616 :param password: password
615 :type password: Optional(str)
617 :type password: Optional(str)
616 :param firstname: firstname
618 :param firstname: firstname
617 :type firstname: Optional(str)
619 :type firstname: Optional(str)
618 :param lastname: lastname
620 :param lastname: lastname
619 :type lastname: Optional(str)
621 :type lastname: Optional(str)
620 :param active: active
622 :param active: active
621 :type active: Optional(bool)
623 :type active: Optional(bool)
622 :param admin: admin
624 :param admin: admin
623 :type admin: Optional(bool)
625 :type admin: Optional(bool)
624 :param extern_name: name of extern
626 :param extern_name: name of extern
625 :type extern_name: Optional(str)
627 :type extern_name: Optional(str)
626 :param extern_type: extern_type
628 :param extern_type: extern_type
627 :type extern_type: Optional(str)
629 :type extern_type: Optional(str)
628
630
629
631
630 OUTPUT::
632 OUTPUT::
631
633
632 id : <id_given_in_input>
634 id : <id_given_in_input>
633 result: {
635 result: {
634 "msg" : "created new user `<username>`",
636 "msg" : "created new user `<username>`",
635 "user": <user_obj>
637 "user": <user_obj>
636 }
638 }
637 error: null
639 error: null
638
640
639 ERROR OUTPUT::
641 ERROR OUTPUT::
640
642
641 id : <id_given_in_input>
643 id : <id_given_in_input>
642 result : null
644 result : null
643 error : {
645 error : {
644 "user `<username>` already exist"
646 "user `<username>` already exist"
645 or
647 or
646 "email `<email>` already exist"
648 "email `<email>` already exist"
647 or
649 or
648 "failed to create user `<username>`"
650 "failed to create user `<username>`"
649 }
651 }
650
652
651 """
653 """
652
654
653 if User.get_by_username(username):
655 if User.get_by_username(username):
654 raise JSONRPCError("user `%s` already exist" % (username,))
656 raise JSONRPCError("user `%s` already exist" % (username,))
655
657
656 if User.get_by_email(email):
658 if User.get_by_email(email):
657 raise JSONRPCError("email `%s` already exist" % (email,))
659 raise JSONRPCError("email `%s` already exist" % (email,))
658
660
659 try:
661 try:
660 user = UserModel().create_or_update(
662 user = UserModel().create_or_update(
661 username=Optional.extract(username),
663 username=Optional.extract(username),
662 password=Optional.extract(password),
664 password=Optional.extract(password),
663 email=Optional.extract(email),
665 email=Optional.extract(email),
664 firstname=Optional.extract(firstname),
666 firstname=Optional.extract(firstname),
665 lastname=Optional.extract(lastname),
667 lastname=Optional.extract(lastname),
666 active=Optional.extract(active),
668 active=Optional.extract(active),
667 admin=Optional.extract(admin),
669 admin=Optional.extract(admin),
668 extern_type=Optional.extract(extern_type),
670 extern_type=Optional.extract(extern_type),
669 extern_name=Optional.extract(extern_name)
671 extern_name=Optional.extract(extern_name)
670 )
672 )
671 Session().commit()
673 Session().commit()
672 return dict(
674 return dict(
673 msg='created new user `%s`' % username,
675 msg='created new user `%s`' % username,
674 user=user.get_api_data()
676 user=user.get_api_data()
675 )
677 )
676 except Exception:
678 except Exception:
677 log.error(traceback.format_exc())
679 log.error(traceback.format_exc())
678 raise JSONRPCError('failed to create user `%s`' % (username,))
680 raise JSONRPCError('failed to create user `%s`' % (username,))
679
681
680 @HasPermissionAnyDecorator('hg.admin')
682 @HasPermissionAnyDecorator('hg.admin')
681 def update_user(self, userid, username=Optional(None),
683 def update_user(self, userid, username=Optional(None),
682 email=Optional(None), password=Optional(None),
684 email=Optional(None), password=Optional(None),
683 firstname=Optional(None), lastname=Optional(None),
685 firstname=Optional(None), lastname=Optional(None),
684 active=Optional(None), admin=Optional(None),
686 active=Optional(None), admin=Optional(None),
685 extern_type=Optional(None), extern_name=Optional(None)):
687 extern_type=Optional(None), extern_name=Optional(None)):
686 """
688 """
687 updates given user if such user exists. This command can
689 updates given user if such user exists. This command can
688 be executed only using api_key belonging to user with admin rights.
690 be executed only using api_key belonging to user with admin rights.
689
691
690 :param userid: userid to update
692 :param userid: userid to update
691 :type userid: str or int
693 :type userid: str or int
692 :param username: new username
694 :param username: new username
693 :type username: str or int
695 :type username: str or int
694 :param email: email
696 :param email: email
695 :type email: str
697 :type email: str
696 :param password: password
698 :param password: password
697 :type password: Optional(str)
699 :type password: Optional(str)
698 :param firstname: firstname
700 :param firstname: firstname
699 :type firstname: Optional(str)
701 :type firstname: Optional(str)
700 :param lastname: lastname
702 :param lastname: lastname
701 :type lastname: Optional(str)
703 :type lastname: Optional(str)
702 :param active: active
704 :param active: active
703 :type active: Optional(bool)
705 :type active: Optional(bool)
704 :param admin: admin
706 :param admin: admin
705 :type admin: Optional(bool)
707 :type admin: Optional(bool)
706 :param extern_name:
708 :param extern_name:
707 :type extern_name: Optional(str)
709 :type extern_name: Optional(str)
708 :param extern_type:
710 :param extern_type:
709 :type extern_type: Optional(str)
711 :type extern_type: Optional(str)
710
712
711
713
712 OUTPUT::
714 OUTPUT::
713
715
714 id : <id_given_in_input>
716 id : <id_given_in_input>
715 result: {
717 result: {
716 "msg" : "updated user ID:<userid> <username>",
718 "msg" : "updated user ID:<userid> <username>",
717 "user": <user_object>,
719 "user": <user_object>,
718 }
720 }
719 error: null
721 error: null
720
722
721 ERROR OUTPUT::
723 ERROR OUTPUT::
722
724
723 id : <id_given_in_input>
725 id : <id_given_in_input>
724 result : null
726 result : null
725 error : {
727 error : {
726 "failed to update user `<username>`"
728 "failed to update user `<username>`"
727 }
729 }
728
730
729 """
731 """
730
732
731 user = get_user_or_error(userid)
733 user = get_user_or_error(userid)
732
734
733 # only non optional arguments will be stored in updates
735 # only non optional arguments will be stored in updates
734 updates = {}
736 updates = {}
735
737
736 try:
738 try:
737
739
738 store_update(updates, username, 'username')
740 store_update(updates, username, 'username')
739 store_update(updates, password, 'password')
741 store_update(updates, password, 'password')
740 store_update(updates, email, 'email')
742 store_update(updates, email, 'email')
741 store_update(updates, firstname, 'name')
743 store_update(updates, firstname, 'name')
742 store_update(updates, lastname, 'lastname')
744 store_update(updates, lastname, 'lastname')
743 store_update(updates, active, 'active')
745 store_update(updates, active, 'active')
744 store_update(updates, admin, 'admin')
746 store_update(updates, admin, 'admin')
745 store_update(updates, extern_name, 'extern_name')
747 store_update(updates, extern_name, 'extern_name')
746 store_update(updates, extern_type, 'extern_type')
748 store_update(updates, extern_type, 'extern_type')
747
749
748 user = UserModel().update_user(user, **updates)
750 user = UserModel().update_user(user, **updates)
749 Session().commit()
751 Session().commit()
750 return dict(
752 return dict(
751 msg='updated user ID:%s %s' % (user.user_id, user.username),
753 msg='updated user ID:%s %s' % (user.user_id, user.username),
752 user=user.get_api_data()
754 user=user.get_api_data()
753 )
755 )
754 except DefaultUserException:
756 except DefaultUserException:
755 log.error(traceback.format_exc())
757 log.error(traceback.format_exc())
756 raise JSONRPCError('editing default user is forbidden')
758 raise JSONRPCError('editing default user is forbidden')
757 except Exception:
759 except Exception:
758 log.error(traceback.format_exc())
760 log.error(traceback.format_exc())
759 raise JSONRPCError('failed to update user `%s`' % (userid,))
761 raise JSONRPCError('failed to update user `%s`' % (userid,))
760
762
761 @HasPermissionAnyDecorator('hg.admin')
763 @HasPermissionAnyDecorator('hg.admin')
762 def delete_user(self, userid):
764 def delete_user(self, userid):
763 """
765 """
764 deletes given user if such user exists. This command can
766 deletes given user if such user exists. This command can
765 be executed only using api_key belonging to user with admin rights.
767 be executed only using api_key belonging to user with admin rights.
766
768
767 :param userid: user to delete
769 :param userid: user to delete
768 :type userid: str or int
770 :type userid: str or int
769
771
770 OUTPUT::
772 OUTPUT::
771
773
772 id : <id_given_in_input>
774 id : <id_given_in_input>
773 result: {
775 result: {
774 "msg" : "deleted user ID:<userid> <username>",
776 "msg" : "deleted user ID:<userid> <username>",
775 "user": null
777 "user": null
776 }
778 }
777 error: null
779 error: null
778
780
779 ERROR OUTPUT::
781 ERROR OUTPUT::
780
782
781 id : <id_given_in_input>
783 id : <id_given_in_input>
782 result : null
784 result : null
783 error : {
785 error : {
784 "failed to delete user ID:<userid> <username>"
786 "failed to delete user ID:<userid> <username>"
785 }
787 }
786
788
787 """
789 """
788 user = get_user_or_error(userid)
790 user = get_user_or_error(userid)
789
791
790 try:
792 try:
791 UserModel().delete(userid)
793 UserModel().delete(userid)
792 Session().commit()
794 Session().commit()
793 return dict(
795 return dict(
794 msg='deleted user ID:%s %s' % (user.user_id, user.username),
796 msg='deleted user ID:%s %s' % (user.user_id, user.username),
795 user=None
797 user=None
796 )
798 )
797 except Exception:
799 except Exception:
798
800
799 log.error(traceback.format_exc())
801 log.error(traceback.format_exc())
800 raise JSONRPCError('failed to delete user ID:%s %s'
802 raise JSONRPCError('failed to delete user ID:%s %s'
801 % (user.user_id, user.username))
803 % (user.user_id, user.username))
802
804
803 # permission check inside
805 # permission check inside
804 def get_user_group(self, usergroupid):
806 def get_user_group(self, usergroupid):
805 """
807 """
806 Gets an existing user group. This command can be executed only using api_key
808 Gets an existing user group. This command can be executed only using api_key
807 belonging to user with admin rights or user who has at least
809 belonging to user with admin rights or user who has at least
808 read access to user group.
810 read access to user group.
809
811
810 :param usergroupid: id of user_group to edit
812 :param usergroupid: id of user_group to edit
811 :type usergroupid: str or int
813 :type usergroupid: str or int
812
814
813 OUTPUT::
815 OUTPUT::
814
816
815 id : <id_given_in_input>
817 id : <id_given_in_input>
816 result : None if group not exist
818 result : None if group not exist
817 {
819 {
818 "users_group_id" : "<id>",
820 "users_group_id" : "<id>",
819 "group_name" : "<groupname>",
821 "group_name" : "<groupname>",
820 "active": "<bool>",
822 "active": "<bool>",
821 "members" : [<user_obj>,...]
823 "members" : [<user_obj>,...]
822 }
824 }
823 error : null
825 error : null
824
826
825 """
827 """
826 user_group = get_user_group_or_error(usergroupid)
828 user_group = get_user_group_or_error(usergroupid)
827 if not HasPermissionAny('hg.admin')():
829 if not HasPermissionAny('hg.admin')():
828 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
830 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
829 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
831 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
830
832
831 data = user_group.get_api_data()
833 data = user_group.get_api_data()
832 return data
834 return data
833
835
834 # permission check inside
836 # permission check inside
835 def get_user_groups(self):
837 def get_user_groups(self):
836 """
838 """
837 Lists all existing user groups. This command can be executed only using
839 Lists all existing user groups. This command can be executed only using
838 api_key belonging to user with admin rights or user who has at least
840 api_key belonging to user with admin rights or user who has at least
839 read access to user group.
841 read access to user group.
840
842
841
843
842 OUTPUT::
844 OUTPUT::
843
845
844 id : <id_given_in_input>
846 id : <id_given_in_input>
845 result : [<user_group_obj>,...]
847 result : [<user_group_obj>,...]
846 error : null
848 error : null
847 """
849 """
848
850
849 return [
851 return [
850 user_group.get_api_data()
852 user_group.get_api_data()
851 for user_group in UserGroupList(UserGroup.query().all(), perm_level='read')
853 for user_group in UserGroupList(UserGroup.query().all(), perm_level='read')
852 ]
854 ]
853
855
854 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
856 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
855 def create_user_group(self, group_name, description=Optional(u''),
857 def create_user_group(self, group_name, description=Optional(u''),
856 owner=Optional(OAttr('apiuser')), active=Optional(True)):
858 owner=Optional(OAttr('apiuser')), active=Optional(True)):
857 """
859 """
858 Creates new user group. This command can be executed only using api_key
860 Creates new user group. This command can be executed only using api_key
859 belonging to user with admin rights or an user who has create user group
861 belonging to user with admin rights or an user who has create user group
860 permission
862 permission
861
863
862 :param group_name: name of new user group
864 :param group_name: name of new user group
863 :type group_name: str
865 :type group_name: str
864 :param description: group description
866 :param description: group description
865 :type description: str
867 :type description: str
866 :param owner: owner of group. If not passed apiuser is the owner
868 :param owner: owner of group. If not passed apiuser is the owner
867 :type owner: Optional(str or int)
869 :type owner: Optional(str or int)
868 :param active: group is active
870 :param active: group is active
869 :type active: Optional(bool)
871 :type active: Optional(bool)
870
872
871 OUTPUT::
873 OUTPUT::
872
874
873 id : <id_given_in_input>
875 id : <id_given_in_input>
874 result: {
876 result: {
875 "msg": "created new user group `<groupname>`",
877 "msg": "created new user group `<groupname>`",
876 "user_group": <user_group_object>
878 "user_group": <user_group_object>
877 }
879 }
878 error: null
880 error: null
879
881
880 ERROR OUTPUT::
882 ERROR OUTPUT::
881
883
882 id : <id_given_in_input>
884 id : <id_given_in_input>
883 result : null
885 result : null
884 error : {
886 error : {
885 "user group `<group name>` already exist"
887 "user group `<group name>` already exist"
886 or
888 or
887 "failed to create group `<group name>`"
889 "failed to create group `<group name>`"
888 }
890 }
889
891
890 """
892 """
891
893
892 if UserGroupModel().get_by_name(group_name):
894 if UserGroupModel().get_by_name(group_name):
893 raise JSONRPCError("user group `%s` already exist" % (group_name,))
895 raise JSONRPCError("user group `%s` already exist" % (group_name,))
894
896
895 try:
897 try:
896 if isinstance(owner, Optional):
898 if isinstance(owner, Optional):
897 owner = request.authuser.user_id
899 owner = request.authuser.user_id
898
900
899 owner = get_user_or_error(owner)
901 owner = get_user_or_error(owner)
900 active = Optional.extract(active)
902 active = Optional.extract(active)
901 description = Optional.extract(description)
903 description = Optional.extract(description)
902 ug = UserGroupModel().create(name=group_name, description=description,
904 ug = UserGroupModel().create(name=group_name, description=description,
903 owner=owner, active=active)
905 owner=owner, active=active)
904 Session().commit()
906 Session().commit()
905 return dict(
907 return dict(
906 msg='created new user group `%s`' % group_name,
908 msg='created new user group `%s`' % group_name,
907 user_group=ug.get_api_data()
909 user_group=ug.get_api_data()
908 )
910 )
909 except Exception:
911 except Exception:
910 log.error(traceback.format_exc())
912 log.error(traceback.format_exc())
911 raise JSONRPCError('failed to create group `%s`' % (group_name,))
913 raise JSONRPCError('failed to create group `%s`' % (group_name,))
912
914
913 # permission check inside
915 # permission check inside
914 def update_user_group(self, usergroupid, group_name=Optional(''),
916 def update_user_group(self, usergroupid, group_name=Optional(''),
915 description=Optional(''), owner=Optional(None),
917 description=Optional(''), owner=Optional(None),
916 active=Optional(True)):
918 active=Optional(True)):
917 """
919 """
918 Updates given usergroup. This command can be executed only using api_key
920 Updates given usergroup. This command can be executed only using api_key
919 belonging to user with admin rights or an admin of given user group
921 belonging to user with admin rights or an admin of given user group
920
922
921 :param usergroupid: id of user group to update
923 :param usergroupid: id of user group to update
922 :type usergroupid: str or int
924 :type usergroupid: str or int
923 :param group_name: name of new user group
925 :param group_name: name of new user group
924 :type group_name: str
926 :type group_name: str
925 :param description: group description
927 :param description: group description
926 :type description: str
928 :type description: str
927 :param owner: owner of group.
929 :param owner: owner of group.
928 :type owner: Optional(str or int)
930 :type owner: Optional(str or int)
929 :param active: group is active
931 :param active: group is active
930 :type active: Optional(bool)
932 :type active: Optional(bool)
931
933
932 OUTPUT::
934 OUTPUT::
933
935
934 id : <id_given_in_input>
936 id : <id_given_in_input>
935 result : {
937 result : {
936 "msg": 'updated user group ID:<user group id> <user group name>',
938 "msg": 'updated user group ID:<user group id> <user group name>',
937 "user_group": <user_group_object>
939 "user_group": <user_group_object>
938 }
940 }
939 error : null
941 error : null
940
942
941 ERROR OUTPUT::
943 ERROR OUTPUT::
942
944
943 id : <id_given_in_input>
945 id : <id_given_in_input>
944 result : null
946 result : null
945 error : {
947 error : {
946 "failed to update user group `<user group name>`"
948 "failed to update user group `<user group name>`"
947 }
949 }
948
950
949 """
951 """
950 user_group = get_user_group_or_error(usergroupid)
952 user_group = get_user_group_or_error(usergroupid)
951 if not HasPermissionAny('hg.admin')():
953 if not HasPermissionAny('hg.admin')():
952 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
954 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
953 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
955 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
954
956
955 if not isinstance(owner, Optional):
957 if not isinstance(owner, Optional):
956 owner = get_user_or_error(owner)
958 owner = get_user_or_error(owner)
957
959
958 updates = {}
960 updates = {}
959 store_update(updates, group_name, 'users_group_name')
961 store_update(updates, group_name, 'users_group_name')
960 store_update(updates, description, 'user_group_description')
962 store_update(updates, description, 'user_group_description')
961 store_update(updates, owner, 'owner')
963 store_update(updates, owner, 'owner')
962 store_update(updates, active, 'users_group_active')
964 store_update(updates, active, 'users_group_active')
963 try:
965 try:
964 UserGroupModel().update(user_group, updates)
966 UserGroupModel().update(user_group, updates)
965 Session().commit()
967 Session().commit()
966 return dict(
968 return dict(
967 msg='updated user group ID:%s %s' % (user_group.users_group_id,
969 msg='updated user group ID:%s %s' % (user_group.users_group_id,
968 user_group.users_group_name),
970 user_group.users_group_name),
969 user_group=user_group.get_api_data()
971 user_group=user_group.get_api_data()
970 )
972 )
971 except Exception:
973 except Exception:
972 log.error(traceback.format_exc())
974 log.error(traceback.format_exc())
973 raise JSONRPCError('failed to update user group `%s`' % (usergroupid,))
975 raise JSONRPCError('failed to update user group `%s`' % (usergroupid,))
974
976
975 # permission check inside
977 # permission check inside
976 def delete_user_group(self, usergroupid):
978 def delete_user_group(self, usergroupid):
977 """
979 """
978 Delete given user group by user group id or name.
980 Delete given user group by user group id or name.
979 This command can be executed only using api_key
981 This command can be executed only using api_key
980 belonging to user with admin rights or an admin of given user group
982 belonging to user with admin rights or an admin of given user group
981
983
982 :param usergroupid:
984 :param usergroupid:
983 :type usergroupid: int
985 :type usergroupid: int
984
986
985 OUTPUT::
987 OUTPUT::
986
988
987 id : <id_given_in_input>
989 id : <id_given_in_input>
988 result : {
990 result : {
989 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
991 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
990 }
992 }
991 error : null
993 error : null
992
994
993 ERROR OUTPUT::
995 ERROR OUTPUT::
994
996
995 id : <id_given_in_input>
997 id : <id_given_in_input>
996 result : null
998 result : null
997 error : {
999 error : {
998 "failed to delete user group ID:<user_group_id> <user_group_name>"
1000 "failed to delete user group ID:<user_group_id> <user_group_name>"
999 or
1001 or
1000 "RepoGroup assigned to <repo_groups_list>"
1002 "RepoGroup assigned to <repo_groups_list>"
1001 }
1003 }
1002
1004
1003 """
1005 """
1004 user_group = get_user_group_or_error(usergroupid)
1006 user_group = get_user_group_or_error(usergroupid)
1005 if not HasPermissionAny('hg.admin')():
1007 if not HasPermissionAny('hg.admin')():
1006 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1008 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1007 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1009 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1008
1010
1009 try:
1011 try:
1010 UserGroupModel().delete(user_group)
1012 UserGroupModel().delete(user_group)
1011 Session().commit()
1013 Session().commit()
1012 return dict(
1014 return dict(
1013 msg='deleted user group ID:%s %s' %
1015 msg='deleted user group ID:%s %s' %
1014 (user_group.users_group_id, user_group.users_group_name),
1016 (user_group.users_group_id, user_group.users_group_name),
1015 user_group=None
1017 user_group=None
1016 )
1018 )
1017 except UserGroupsAssignedException as e:
1019 except UserGroupsAssignedException as e:
1018 log.error(traceback.format_exc())
1020 log.error(traceback.format_exc())
1019 raise JSONRPCError(str(e))
1021 raise JSONRPCError(str(e))
1020 except Exception:
1022 except Exception:
1021 log.error(traceback.format_exc())
1023 log.error(traceback.format_exc())
1022 raise JSONRPCError('failed to delete user group ID:%s %s' %
1024 raise JSONRPCError('failed to delete user group ID:%s %s' %
1023 (user_group.users_group_id,
1025 (user_group.users_group_id,
1024 user_group.users_group_name)
1026 user_group.users_group_name)
1025 )
1027 )
1026
1028
1027 # permission check inside
1029 # permission check inside
1028 def add_user_to_user_group(self, usergroupid, userid):
1030 def add_user_to_user_group(self, usergroupid, userid):
1029 """
1031 """
1030 Adds a user to a user group. If user exists in that group success will be
1032 Adds a user to a user group. If user exists in that group success will be
1031 `false`. This command can be executed only using api_key
1033 `false`. This command can be executed only using api_key
1032 belonging to user with admin rights or an admin of given user group
1034 belonging to user with admin rights or an admin of given user group
1033
1035
1034 :param usergroupid:
1036 :param usergroupid:
1035 :type usergroupid: int
1037 :type usergroupid: int
1036 :param userid:
1038 :param userid:
1037 :type userid: int
1039 :type userid: int
1038
1040
1039 OUTPUT::
1041 OUTPUT::
1040
1042
1041 id : <id_given_in_input>
1043 id : <id_given_in_input>
1042 result : {
1044 result : {
1043 "success": True|False # depends on if member is in group
1045 "success": True|False # depends on if member is in group
1044 "msg": "added member `<username>` to user group `<groupname>` |
1046 "msg": "added member `<username>` to user group `<groupname>` |
1045 User is already in that group"
1047 User is already in that group"
1046
1048
1047 }
1049 }
1048 error : null
1050 error : null
1049
1051
1050 ERROR OUTPUT::
1052 ERROR OUTPUT::
1051
1053
1052 id : <id_given_in_input>
1054 id : <id_given_in_input>
1053 result : null
1055 result : null
1054 error : {
1056 error : {
1055 "failed to add member to user group `<user_group_name>`"
1057 "failed to add member to user group `<user_group_name>`"
1056 }
1058 }
1057
1059
1058 """
1060 """
1059 user = get_user_or_error(userid)
1061 user = get_user_or_error(userid)
1060 user_group = get_user_group_or_error(usergroupid)
1062 user_group = get_user_group_or_error(usergroupid)
1061 if not HasPermissionAny('hg.admin')():
1063 if not HasPermissionAny('hg.admin')():
1062 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1064 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1063 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1065 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1064
1066
1065 try:
1067 try:
1066 ugm = UserGroupModel().add_user_to_group(user_group, user)
1068 ugm = UserGroupModel().add_user_to_group(user_group, user)
1067 success = True if ugm != True else False
1069 success = True if ugm != True else False
1068 msg = 'added member `%s` to user group `%s`' % (
1070 msg = 'added member `%s` to user group `%s`' % (
1069 user.username, user_group.users_group_name
1071 user.username, user_group.users_group_name
1070 )
1072 )
1071 msg = msg if success else 'User is already in that group'
1073 msg = msg if success else 'User is already in that group'
1072 Session().commit()
1074 Session().commit()
1073
1075
1074 return dict(
1076 return dict(
1075 success=success,
1077 success=success,
1076 msg=msg
1078 msg=msg
1077 )
1079 )
1078 except Exception:
1080 except Exception:
1079 log.error(traceback.format_exc())
1081 log.error(traceback.format_exc())
1080 raise JSONRPCError(
1082 raise JSONRPCError(
1081 'failed to add member to user group `%s`' % (
1083 'failed to add member to user group `%s`' % (
1082 user_group.users_group_name,
1084 user_group.users_group_name,
1083 )
1085 )
1084 )
1086 )
1085
1087
1086 # permission check inside
1088 # permission check inside
1087 def remove_user_from_user_group(self, usergroupid, userid):
1089 def remove_user_from_user_group(self, usergroupid, userid):
1088 """
1090 """
1089 Removes a user from a user group. If user is not in given group success will
1091 Removes a user from a user group. If user is not in given group success will
1090 be `false`. This command can be executed only
1092 be `false`. This command can be executed only
1091 using api_key belonging to user with admin rights or an admin of given user group
1093 using api_key belonging to user with admin rights or an admin of given user group
1092
1094
1093 :param usergroupid:
1095 :param usergroupid:
1094 :param userid:
1096 :param userid:
1095
1097
1096
1098
1097 OUTPUT::
1099 OUTPUT::
1098
1100
1099 id : <id_given_in_input>
1101 id : <id_given_in_input>
1100 result: {
1102 result: {
1101 "success": True|False, # depends on if member is in group
1103 "success": True|False, # depends on if member is in group
1102 "msg": "removed member <username> from user group <groupname> |
1104 "msg": "removed member <username> from user group <groupname> |
1103 User wasn't in group"
1105 User wasn't in group"
1104 }
1106 }
1105 error: null
1107 error: null
1106
1108
1107 """
1109 """
1108 user = get_user_or_error(userid)
1110 user = get_user_or_error(userid)
1109 user_group = get_user_group_or_error(usergroupid)
1111 user_group = get_user_group_or_error(usergroupid)
1110 if not HasPermissionAny('hg.admin')():
1112 if not HasPermissionAny('hg.admin')():
1111 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1113 if not HasUserGroupPermissionLevel('admin')(user_group.users_group_name):
1112 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1114 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1113
1115
1114 try:
1116 try:
1115 success = UserGroupModel().remove_user_from_group(user_group, user)
1117 success = UserGroupModel().remove_user_from_group(user_group, user)
1116 msg = 'removed member `%s` from user group `%s`' % (
1118 msg = 'removed member `%s` from user group `%s`' % (
1117 user.username, user_group.users_group_name
1119 user.username, user_group.users_group_name
1118 )
1120 )
1119 msg = msg if success else "User wasn't in group"
1121 msg = msg if success else "User wasn't in group"
1120 Session().commit()
1122 Session().commit()
1121 return dict(success=success, msg=msg)
1123 return dict(success=success, msg=msg)
1122 except Exception:
1124 except Exception:
1123 log.error(traceback.format_exc())
1125 log.error(traceback.format_exc())
1124 raise JSONRPCError(
1126 raise JSONRPCError(
1125 'failed to remove member from user group `%s`' % (
1127 'failed to remove member from user group `%s`' % (
1126 user_group.users_group_name,
1128 user_group.users_group_name,
1127 )
1129 )
1128 )
1130 )
1129
1131
1130 # permission check inside
1132 # permission check inside
1131 def get_repo(self, repoid,
1133 def get_repo(self, repoid,
1132 with_revision_names=Optional(False),
1134 with_revision_names=Optional(False),
1133 with_pullrequests=Optional(False)):
1135 with_pullrequests=Optional(False)):
1134 """
1136 """
1135 Gets an existing repository by it's name or repository_id. Members will return
1137 Gets an existing repository by it's name or repository_id. Members will return
1136 either users_group or user associated to that repository. This command can be
1138 either users_group or user associated to that repository. This command can be
1137 executed only using api_key belonging to user with admin
1139 executed only using api_key belonging to user with admin
1138 rights or regular user that have at least read access to repository.
1140 rights or regular user that have at least read access to repository.
1139
1141
1140 :param repoid: repository name or repository id
1142 :param repoid: repository name or repository id
1141 :type repoid: str or int
1143 :type repoid: str or int
1142
1144
1143 OUTPUT::
1145 OUTPUT::
1144
1146
1145 id : <id_given_in_input>
1147 id : <id_given_in_input>
1146 result : {
1148 result : {
1147 {
1149 {
1148 "repo_id" : "<repo_id>",
1150 "repo_id" : "<repo_id>",
1149 "repo_name" : "<reponame>"
1151 "repo_name" : "<reponame>"
1150 "repo_type" : "<repo_type>",
1152 "repo_type" : "<repo_type>",
1151 "clone_uri" : "<clone_uri>",
1153 "clone_uri" : "<clone_uri>",
1152 "enable_downloads": "<bool>",
1154 "enable_downloads": "<bool>",
1153 "enable_locking": "<bool>",
1155 "enable_locking": "<bool>",
1154 "enable_statistics": "<bool>",
1156 "enable_statistics": "<bool>",
1155 "private": "<bool>",
1157 "private": "<bool>",
1156 "created_on" : "<date_time_created>",
1158 "created_on" : "<date_time_created>",
1157 "description" : "<description>",
1159 "description" : "<description>",
1158 "landing_rev": "<landing_rev>",
1160 "landing_rev": "<landing_rev>",
1159 "last_changeset": {
1161 "last_changeset": {
1160 "author": "<full_author>",
1162 "author": "<full_author>",
1161 "date": "<date_time_of_commit>",
1163 "date": "<date_time_of_commit>",
1162 "message": "<commit_message>",
1164 "message": "<commit_message>",
1163 "raw_id": "<raw_id>",
1165 "raw_id": "<raw_id>",
1164 "revision": "<numeric_revision>",
1166 "revision": "<numeric_revision>",
1165 "short_id": "<short_id>"
1167 "short_id": "<short_id>"
1166 }
1168 }
1167 "owner": "<repo_owner>",
1169 "owner": "<repo_owner>",
1168 "fork_of": "<name_of_fork_parent>",
1170 "fork_of": "<name_of_fork_parent>",
1169 "members" : [
1171 "members" : [
1170 {
1172 {
1171 "name": "<username>",
1173 "name": "<username>",
1172 "type" : "user",
1174 "type" : "user",
1173 "permission" : "repository.(read|write|admin)"
1175 "permission" : "repository.(read|write|admin)"
1174 },
1176 },
1175 …
1177 …
1176 {
1178 {
1177 "name": "<usergroup name>",
1179 "name": "<usergroup name>",
1178 "type" : "user_group",
1180 "type" : "user_group",
1179 "permission" : "usergroup.(read|write|admin)"
1181 "permission" : "usergroup.(read|write|admin)"
1180 },
1182 },
1181 …
1183 …
1182 ]
1184 ]
1183 "followers": [<user_obj>, ...],
1185 "followers": [<user_obj>, ...],
1184 <if with_revision_names == True>
1186 <if with_revision_names == True>
1185 "tags": {
1187 "tags": {
1186 "<tagname>": "<raw_id>",
1188 "<tagname>": "<raw_id>",
1187 ...
1189 ...
1188 },
1190 },
1189 "branches": {
1191 "branches": {
1190 "<branchname>": "<raw_id>",
1192 "<branchname>": "<raw_id>",
1191 ...
1193 ...
1192 },
1194 },
1193 "bookmarks": {
1195 "bookmarks": {
1194 "<bookmarkname>": "<raw_id>",
1196 "<bookmarkname>": "<raw_id>",
1195 ...
1197 ...
1196 },
1198 },
1197 }
1199 }
1198 }
1200 }
1199 error : null
1201 error : null
1200
1202
1201 """
1203 """
1202 repo = get_repo_or_error(repoid)
1204 repo = get_repo_or_error(repoid)
1203
1205
1204 if not HasPermissionAny('hg.admin')():
1206 if not HasPermissionAny('hg.admin')():
1205 if not HasRepoPermissionLevel('read')(repo.repo_name):
1207 if not HasRepoPermissionLevel('read')(repo.repo_name):
1206 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1208 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1207
1209
1208 members = []
1210 members = []
1209 for user in repo.repo_to_perm:
1211 for user in repo.repo_to_perm:
1210 perm = user.permission.permission_name
1212 perm = user.permission.permission_name
1211 user = user.user
1213 user = user.user
1212 user_data = {
1214 user_data = {
1213 'name': user.username,
1215 'name': user.username,
1214 'type': "user",
1216 'type': "user",
1215 'permission': perm
1217 'permission': perm
1216 }
1218 }
1217 members.append(user_data)
1219 members.append(user_data)
1218
1220
1219 for user_group in repo.users_group_to_perm:
1221 for user_group in repo.users_group_to_perm:
1220 perm = user_group.permission.permission_name
1222 perm = user_group.permission.permission_name
1221 user_group = user_group.users_group
1223 user_group = user_group.users_group
1222 user_group_data = {
1224 user_group_data = {
1223 'name': user_group.users_group_name,
1225 'name': user_group.users_group_name,
1224 'type': "user_group",
1226 'type': "user_group",
1225 'permission': perm
1227 'permission': perm
1226 }
1228 }
1227 members.append(user_group_data)
1229 members.append(user_group_data)
1228
1230
1229 followers = [
1231 followers = [
1230 uf.user.get_api_data()
1232 uf.user.get_api_data()
1231 for uf in repo.followers
1233 for uf in repo.followers
1232 ]
1234 ]
1233
1235
1234 data = repo.get_api_data(with_revision_names=Optional.extract(with_revision_names),
1236 data = repo.get_api_data(with_revision_names=Optional.extract(with_revision_names),
1235 with_pullrequests=Optional.extract(with_pullrequests))
1237 with_pullrequests=Optional.extract(with_pullrequests))
1236 data['members'] = members
1238 data['members'] = members
1237 data['followers'] = followers
1239 data['followers'] = followers
1238 return data
1240 return data
1239
1241
1240 # permission check inside
1242 # permission check inside
1241 def get_repos(self):
1243 def get_repos(self):
1242 """
1244 """
1243 Lists all existing repositories. This command can be executed only using
1245 Lists all existing repositories. This command can be executed only using
1244 api_key belonging to user with admin rights or regular user that have
1246 api_key belonging to user with admin rights or regular user that have
1245 admin, write or read access to repository.
1247 admin, write or read access to repository.
1246
1248
1247
1249
1248 OUTPUT::
1250 OUTPUT::
1249
1251
1250 id : <id_given_in_input>
1252 id : <id_given_in_input>
1251 result: [
1253 result: [
1252 {
1254 {
1253 "repo_id" : "<repo_id>",
1255 "repo_id" : "<repo_id>",
1254 "repo_name" : "<reponame>"
1256 "repo_name" : "<reponame>"
1255 "repo_type" : "<repo_type>",
1257 "repo_type" : "<repo_type>",
1256 "clone_uri" : "<clone_uri>",
1258 "clone_uri" : "<clone_uri>",
1257 "private": : "<bool>",
1259 "private": : "<bool>",
1258 "created_on" : "<datetimecreated>",
1260 "created_on" : "<datetimecreated>",
1259 "description" : "<description>",
1261 "description" : "<description>",
1260 "landing_rev": "<landing_rev>",
1262 "landing_rev": "<landing_rev>",
1261 "owner": "<repo_owner>",
1263 "owner": "<repo_owner>",
1262 "fork_of": "<name_of_fork_parent>",
1264 "fork_of": "<name_of_fork_parent>",
1263 "enable_downloads": "<bool>",
1265 "enable_downloads": "<bool>",
1264 "enable_locking": "<bool>",
1266 "enable_locking": "<bool>",
1265 "enable_statistics": "<bool>",
1267 "enable_statistics": "<bool>",
1266 },
1268 },
1267 …
1269 …
1268 ]
1270 ]
1269 error: null
1271 error: null
1270 """
1272 """
1271 if not HasPermissionAny('hg.admin')():
1273 if not HasPermissionAny('hg.admin')():
1272 repos = RepoModel().get_all_user_repos(user=request.authuser.user_id)
1274 repos = RepoModel().get_all_user_repos(user=request.authuser.user_id)
1273 else:
1275 else:
1274 repos = Repository.query()
1276 repos = Repository.query()
1275
1277
1276 return [
1278 return [
1277 repo.get_api_data()
1279 repo.get_api_data()
1278 for repo in repos
1280 for repo in repos
1279 ]
1281 ]
1280
1282
1281 # permission check inside
1283 # permission check inside
1282 def get_repo_nodes(self, repoid, revision, root_path,
1284 def get_repo_nodes(self, repoid, revision, root_path,
1283 ret_type=Optional('all')):
1285 ret_type=Optional('all')):
1284 """
1286 """
1285 returns a list of nodes and it's children in a flat list for a given path
1287 returns a list of nodes and it's children in a flat list for a given path
1286 at given revision. It's possible to specify ret_type to show only `files` or
1288 at given revision. It's possible to specify ret_type to show only `files` or
1287 `dirs`. This command can be executed only using api_key belonging to
1289 `dirs`. This command can be executed only using api_key belonging to
1288 user with admin rights or regular user that have at least read access to repository.
1290 user with admin rights or regular user that have at least read access to repository.
1289
1291
1290 :param repoid: repository name or repository id
1292 :param repoid: repository name or repository id
1291 :type repoid: str or int
1293 :type repoid: str or int
1292 :param revision: revision for which listing should be done
1294 :param revision: revision for which listing should be done
1293 :type revision: str
1295 :type revision: str
1294 :param root_path: path from which start displaying
1296 :param root_path: path from which start displaying
1295 :type root_path: str
1297 :type root_path: str
1296 :param ret_type: return type 'all|files|dirs' nodes
1298 :param ret_type: return type 'all|files|dirs' nodes
1297 :type ret_type: Optional(str)
1299 :type ret_type: Optional(str)
1298
1300
1299
1301
1300 OUTPUT::
1302 OUTPUT::
1301
1303
1302 id : <id_given_in_input>
1304 id : <id_given_in_input>
1303 result: [
1305 result: [
1304 {
1306 {
1305 "name" : "<name>"
1307 "name" : "<name>"
1306 "type" : "<type>",
1308 "type" : "<type>",
1307 },
1309 },
1308 …
1310 …
1309 ]
1311 ]
1310 error: null
1312 error: null
1311 """
1313 """
1312 repo = get_repo_or_error(repoid)
1314 repo = get_repo_or_error(repoid)
1313
1315
1314 if not HasPermissionAny('hg.admin')():
1316 if not HasPermissionAny('hg.admin')():
1315 if not HasRepoPermissionLevel('read')(repo.repo_name):
1317 if not HasRepoPermissionLevel('read')(repo.repo_name):
1316 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1318 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1317
1319
1318 ret_type = Optional.extract(ret_type)
1320 ret_type = Optional.extract(ret_type)
1319 _map = {}
1321 _map = {}
1320 try:
1322 try:
1321 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
1323 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
1322 flat=False)
1324 flat=False)
1323 _map = {
1325 _map = {
1324 'all': _d + _f,
1326 'all': _d + _f,
1325 'files': _f,
1327 'files': _f,
1326 'dirs': _d,
1328 'dirs': _d,
1327 }
1329 }
1328 return _map[ret_type]
1330 return _map[ret_type]
1329 except KeyError:
1331 except KeyError:
1330 raise JSONRPCError('ret_type must be one of %s'
1332 raise JSONRPCError('ret_type must be one of %s'
1331 % (','.join(_map.keys())))
1333 % (','.join(_map.keys())))
1332 except Exception:
1334 except Exception:
1333 log.error(traceback.format_exc())
1335 log.error(traceback.format_exc())
1334 raise JSONRPCError(
1336 raise JSONRPCError(
1335 'failed to get repo: `%s` nodes' % repo.repo_name
1337 'failed to get repo: `%s` nodes' % repo.repo_name
1336 )
1338 )
1337
1339
1338 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
1340 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
1339 def create_repo(self, repo_name, owner=Optional(OAttr('apiuser')),
1341 def create_repo(self, repo_name, owner=Optional(OAttr('apiuser')),
1340 repo_type=Optional('hg'), description=Optional(''),
1342 repo_type=Optional('hg'), description=Optional(''),
1341 private=Optional(False), clone_uri=Optional(None),
1343 private=Optional(False), clone_uri=Optional(None),
1342 landing_rev=Optional('rev:tip'),
1344 landing_rev=Optional('rev:tip'),
1343 enable_statistics=Optional(False),
1345 enable_statistics=Optional(False),
1344 enable_locking=Optional(False),
1346 enable_locking=Optional(False),
1345 enable_downloads=Optional(False),
1347 enable_downloads=Optional(False),
1346 copy_permissions=Optional(False)):
1348 copy_permissions=Optional(False)):
1347 """
1349 """
1348 Creates a repository. If repository name contains "/", all needed repository
1350 Creates a repository. If repository name contains "/", all needed repository
1349 groups will be created. For example "foo/bar/baz" will create groups
1351 groups will be created. For example "foo/bar/baz" will create groups
1350 "foo", "bar" (with "foo" as parent), and create "baz" repository with
1352 "foo", "bar" (with "foo" as parent), and create "baz" repository with
1351 "bar" as group. This command can be executed only using api_key
1353 "bar" as group. This command can be executed only using api_key
1352 belonging to user with admin rights or regular user that have create
1354 belonging to user with admin rights or regular user that have create
1353 repository permission. Regular users cannot specify owner parameter
1355 repository permission. Regular users cannot specify owner parameter
1354
1356
1355 :param repo_name: repository name
1357 :param repo_name: repository name
1356 :type repo_name: str
1358 :type repo_name: str
1357 :param owner: user_id or username
1359 :param owner: user_id or username
1358 :type owner: Optional(str)
1360 :type owner: Optional(str)
1359 :param repo_type: 'hg' or 'git'
1361 :param repo_type: 'hg' or 'git'
1360 :type repo_type: Optional(str)
1362 :type repo_type: Optional(str)
1361 :param description: repository description
1363 :param description: repository description
1362 :type description: Optional(str)
1364 :type description: Optional(str)
1363 :param private:
1365 :param private:
1364 :type private: bool
1366 :type private: bool
1365 :param clone_uri:
1367 :param clone_uri:
1366 :type clone_uri: str
1368 :type clone_uri: str
1367 :param landing_rev: <rev_type>:<rev>
1369 :param landing_rev: <rev_type>:<rev>
1368 :type landing_rev: str
1370 :type landing_rev: str
1369 :param enable_locking:
1371 :param enable_locking:
1370 :type enable_locking: bool
1372 :type enable_locking: bool
1371 :param enable_downloads:
1373 :param enable_downloads:
1372 :type enable_downloads: bool
1374 :type enable_downloads: bool
1373 :param enable_statistics:
1375 :param enable_statistics:
1374 :type enable_statistics: bool
1376 :type enable_statistics: bool
1375 :param copy_permissions: Copy permission from group that repository is
1377 :param copy_permissions: Copy permission from group that repository is
1376 being created.
1378 being created.
1377 :type copy_permissions: bool
1379 :type copy_permissions: bool
1378
1380
1379 OUTPUT::
1381 OUTPUT::
1380
1382
1381 id : <id_given_in_input>
1383 id : <id_given_in_input>
1382 result: {
1384 result: {
1383 "msg": "Created new repository `<reponame>`",
1385 "msg": "Created new repository `<reponame>`",
1384 "success": true,
1386 "success": true,
1385 "task": "<celery task id or None if done sync>"
1387 "task": "<celery task id or None if done sync>"
1386 }
1388 }
1387 error: null
1389 error: null
1388
1390
1389 ERROR OUTPUT::
1391 ERROR OUTPUT::
1390
1392
1391 id : <id_given_in_input>
1393 id : <id_given_in_input>
1392 result : null
1394 result : null
1393 error : {
1395 error : {
1394 'failed to create repository `<repo_name>`
1396 'failed to create repository `<repo_name>`
1395 }
1397 }
1396
1398
1397 """
1399 """
1398 if not HasPermissionAny('hg.admin')():
1400 if not HasPermissionAny('hg.admin')():
1399 if not isinstance(owner, Optional):
1401 if not isinstance(owner, Optional):
1400 # forbid setting owner for non-admins
1402 # forbid setting owner for non-admins
1401 raise JSONRPCError(
1403 raise JSONRPCError(
1402 'Only Kallithea admin can specify `owner` param'
1404 'Only Kallithea admin can specify `owner` param'
1403 )
1405 )
1404 if isinstance(owner, Optional):
1406 if isinstance(owner, Optional):
1405 owner = request.authuser.user_id
1407 owner = request.authuser.user_id
1406
1408
1407 owner = get_user_or_error(owner)
1409 owner = get_user_or_error(owner)
1408
1410
1409 if RepoModel().get_by_repo_name(repo_name):
1411 if RepoModel().get_by_repo_name(repo_name):
1410 raise JSONRPCError("repo `%s` already exist" % repo_name)
1412 raise JSONRPCError("repo `%s` already exist" % repo_name)
1411
1413
1412 defs = Setting.get_default_repo_settings(strip_prefix=True)
1414 defs = Setting.get_default_repo_settings(strip_prefix=True)
1413 if isinstance(private, Optional):
1415 if isinstance(private, Optional):
1414 private = defs.get('repo_private') or Optional.extract(private)
1416 private = defs.get('repo_private') or Optional.extract(private)
1415 if isinstance(repo_type, Optional):
1417 if isinstance(repo_type, Optional):
1416 repo_type = defs.get('repo_type')
1418 repo_type = defs.get('repo_type')
1417 if isinstance(enable_statistics, Optional):
1419 if isinstance(enable_statistics, Optional):
1418 enable_statistics = defs.get('repo_enable_statistics')
1420 enable_statistics = defs.get('repo_enable_statistics')
1419 if isinstance(enable_locking, Optional):
1421 if isinstance(enable_locking, Optional):
1420 enable_locking = defs.get('repo_enable_locking')
1422 enable_locking = defs.get('repo_enable_locking')
1421 if isinstance(enable_downloads, Optional):
1423 if isinstance(enable_downloads, Optional):
1422 enable_downloads = defs.get('repo_enable_downloads')
1424 enable_downloads = defs.get('repo_enable_downloads')
1423
1425
1424 clone_uri = Optional.extract(clone_uri)
1426 clone_uri = Optional.extract(clone_uri)
1425 description = Optional.extract(description)
1427 description = Optional.extract(description)
1426 landing_rev = Optional.extract(landing_rev)
1428 landing_rev = Optional.extract(landing_rev)
1427 copy_permissions = Optional.extract(copy_permissions)
1429 copy_permissions = Optional.extract(copy_permissions)
1428
1430
1429 try:
1431 try:
1430 repo_name_cleaned = repo_name.split('/')[-1]
1432 repo_name_cleaned = repo_name.split('/')[-1]
1431 # create structure of groups and return the last group
1433 # create structure of groups and return the last group
1432 repo_group = map_groups(repo_name)
1434 repo_group = map_groups(repo_name)
1433 data = dict(
1435 data = dict(
1434 repo_name=repo_name_cleaned,
1436 repo_name=repo_name_cleaned,
1435 repo_name_full=repo_name,
1437 repo_name_full=repo_name,
1436 repo_type=repo_type,
1438 repo_type=repo_type,
1437 repo_description=description,
1439 repo_description=description,
1438 owner=owner,
1440 owner=owner,
1439 repo_private=private,
1441 repo_private=private,
1440 clone_uri=clone_uri,
1442 clone_uri=clone_uri,
1441 repo_group=repo_group,
1443 repo_group=repo_group,
1442 repo_landing_rev=landing_rev,
1444 repo_landing_rev=landing_rev,
1443 enable_statistics=enable_statistics,
1445 enable_statistics=enable_statistics,
1444 enable_locking=enable_locking,
1446 enable_locking=enable_locking,
1445 enable_downloads=enable_downloads,
1447 enable_downloads=enable_downloads,
1446 repo_copy_permissions=copy_permissions,
1448 repo_copy_permissions=copy_permissions,
1447 )
1449 )
1448
1450
1449 task = RepoModel().create(form_data=data, cur_user=owner)
1451 task = RepoModel().create(form_data=data, cur_user=owner)
1450 task_id = task.task_id
1452 task_id = task.task_id
1451 # no commit, it's done in RepoModel, or async via celery
1453 # no commit, it's done in RepoModel, or async via celery
1452 return dict(
1454 return dict(
1453 msg="Created new repository `%s`" % (repo_name,),
1455 msg="Created new repository `%s`" % (repo_name,),
1454 success=True, # cannot return the repo data here since fork
1456 success=True, # cannot return the repo data here since fork
1455 # can be done async
1457 # can be done async
1456 task=task_id
1458 task=task_id
1457 )
1459 )
1458 except Exception:
1460 except Exception:
1459 log.error(traceback.format_exc())
1461 log.error(traceback.format_exc())
1460 raise JSONRPCError(
1462 raise JSONRPCError(
1461 'failed to create repository `%s`' % (repo_name,))
1463 'failed to create repository `%s`' % (repo_name,))
1462
1464
1463 # permission check inside
1465 # permission check inside
1464 def update_repo(self, repoid, name=Optional(None),
1466 def update_repo(self, repoid, name=Optional(None),
1465 owner=Optional(OAttr('apiuser')),
1467 owner=Optional(OAttr('apiuser')),
1466 group=Optional(None),
1468 group=Optional(None),
1467 description=Optional(''), private=Optional(False),
1469 description=Optional(''), private=Optional(False),
1468 clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
1470 clone_uri=Optional(None), landing_rev=Optional('rev:tip'),
1469 enable_statistics=Optional(False),
1471 enable_statistics=Optional(False),
1470 enable_locking=Optional(False),
1472 enable_locking=Optional(False),
1471 enable_downloads=Optional(False)):
1473 enable_downloads=Optional(False)):
1472
1474
1473 """
1475 """
1474 Updates repo
1476 Updates repo
1475
1477
1476 :param repoid: repository name or repository id
1478 :param repoid: repository name or repository id
1477 :type repoid: str or int
1479 :type repoid: str or int
1478 :param name:
1480 :param name:
1479 :param owner:
1481 :param owner:
1480 :param group:
1482 :param group:
1481 :param description:
1483 :param description:
1482 :param private:
1484 :param private:
1483 :param clone_uri:
1485 :param clone_uri:
1484 :param landing_rev:
1486 :param landing_rev:
1485 :param enable_statistics:
1487 :param enable_statistics:
1486 :param enable_locking:
1488 :param enable_locking:
1487 :param enable_downloads:
1489 :param enable_downloads:
1488 """
1490 """
1489 repo = get_repo_or_error(repoid)
1491 repo = get_repo_or_error(repoid)
1490 if not HasPermissionAny('hg.admin')():
1492 if not HasPermissionAny('hg.admin')():
1491 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1493 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1492 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1494 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1493
1495
1494 if (name != repo.repo_name and
1496 if (name != repo.repo_name and
1495 not HasPermissionAny('hg.create.repository')()
1497 not HasPermissionAny('hg.create.repository')()
1496 ):
1498 ):
1497 raise JSONRPCError('no permission to create (or move) repositories')
1499 raise JSONRPCError('no permission to create (or move) repositories')
1498
1500
1499 if not isinstance(owner, Optional):
1501 if not isinstance(owner, Optional):
1500 # forbid setting owner for non-admins
1502 # forbid setting owner for non-admins
1501 raise JSONRPCError(
1503 raise JSONRPCError(
1502 'Only Kallithea admin can specify `owner` param'
1504 'Only Kallithea admin can specify `owner` param'
1503 )
1505 )
1504
1506
1505 updates = {}
1507 updates = {}
1506 repo_group = group
1508 repo_group = group
1507 if not isinstance(repo_group, Optional):
1509 if not isinstance(repo_group, Optional):
1508 repo_group = get_repo_group_or_error(repo_group)
1510 repo_group = get_repo_group_or_error(repo_group)
1509 repo_group = repo_group.group_id
1511 repo_group = repo_group.group_id
1510 try:
1512 try:
1511 store_update(updates, name, 'repo_name')
1513 store_update(updates, name, 'repo_name')
1512 store_update(updates, repo_group, 'repo_group')
1514 store_update(updates, repo_group, 'repo_group')
1513 store_update(updates, owner, 'owner')
1515 store_update(updates, owner, 'owner')
1514 store_update(updates, description, 'repo_description')
1516 store_update(updates, description, 'repo_description')
1515 store_update(updates, private, 'repo_private')
1517 store_update(updates, private, 'repo_private')
1516 store_update(updates, clone_uri, 'clone_uri')
1518 store_update(updates, clone_uri, 'clone_uri')
1517 store_update(updates, landing_rev, 'repo_landing_rev')
1519 store_update(updates, landing_rev, 'repo_landing_rev')
1518 store_update(updates, enable_statistics, 'repo_enable_statistics')
1520 store_update(updates, enable_statistics, 'repo_enable_statistics')
1519 store_update(updates, enable_locking, 'repo_enable_locking')
1521 store_update(updates, enable_locking, 'repo_enable_locking')
1520 store_update(updates, enable_downloads, 'repo_enable_downloads')
1522 store_update(updates, enable_downloads, 'repo_enable_downloads')
1521
1523
1522 RepoModel().update(repo, **updates)
1524 RepoModel().update(repo, **updates)
1523 Session().commit()
1525 Session().commit()
1524 return dict(
1526 return dict(
1525 msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1527 msg='updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1526 repository=repo.get_api_data()
1528 repository=repo.get_api_data()
1527 )
1529 )
1528 except Exception:
1530 except Exception:
1529 log.error(traceback.format_exc())
1531 log.error(traceback.format_exc())
1530 raise JSONRPCError('failed to update repo `%s`' % repoid)
1532 raise JSONRPCError('failed to update repo `%s`' % repoid)
1531
1533
1532 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
1534 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
1533 def fork_repo(self, repoid, fork_name,
1535 def fork_repo(self, repoid, fork_name,
1534 owner=Optional(OAttr('apiuser')),
1536 owner=Optional(OAttr('apiuser')),
1535 description=Optional(''), copy_permissions=Optional(False),
1537 description=Optional(''), copy_permissions=Optional(False),
1536 private=Optional(False), landing_rev=Optional('rev:tip')):
1538 private=Optional(False), landing_rev=Optional('rev:tip')):
1537 """
1539 """
1538 Creates a fork of given repo. In case of using celery this will
1540 Creates a fork of given repo. In case of using celery this will
1539 immediately return success message, while fork is going to be created
1541 immediately return success message, while fork is going to be created
1540 asynchronous. This command can be executed only using api_key belonging to
1542 asynchronous. This command can be executed only using api_key belonging to
1541 user with admin rights or regular user that have fork permission, and at least
1543 user with admin rights or regular user that have fork permission, and at least
1542 read access to forking repository. Regular users cannot specify owner parameter.
1544 read access to forking repository. Regular users cannot specify owner parameter.
1543
1545
1544 :param repoid: repository name or repository id
1546 :param repoid: repository name or repository id
1545 :type repoid: str or int
1547 :type repoid: str or int
1546 :param fork_name:
1548 :param fork_name:
1547 :param owner:
1549 :param owner:
1548 :param description:
1550 :param description:
1549 :param copy_permissions:
1551 :param copy_permissions:
1550 :param private:
1552 :param private:
1551 :param landing_rev:
1553 :param landing_rev:
1552
1554
1553 INPUT::
1555 INPUT::
1554
1556
1555 id : <id_for_response>
1557 id : <id_for_response>
1556 api_key : "<api_key>"
1558 api_key : "<api_key>"
1557 args: {
1559 args: {
1558 "repoid" : "<reponame or repo_id>",
1560 "repoid" : "<reponame or repo_id>",
1559 "fork_name": "<forkname>",
1561 "fork_name": "<forkname>",
1560 "owner": "<username or user_id = Optional(=apiuser)>",
1562 "owner": "<username or user_id = Optional(=apiuser)>",
1561 "description": "<description>",
1563 "description": "<description>",
1562 "copy_permissions": "<bool>",
1564 "copy_permissions": "<bool>",
1563 "private": "<bool>",
1565 "private": "<bool>",
1564 "landing_rev": "<landing_rev>"
1566 "landing_rev": "<landing_rev>"
1565 }
1567 }
1566
1568
1567 OUTPUT::
1569 OUTPUT::
1568
1570
1569 id : <id_given_in_input>
1571 id : <id_given_in_input>
1570 result: {
1572 result: {
1571 "msg": "Created fork of `<reponame>` as `<forkname>`",
1573 "msg": "Created fork of `<reponame>` as `<forkname>`",
1572 "success": true,
1574 "success": true,
1573 "task": "<celery task id or None if done sync>"
1575 "task": "<celery task id or None if done sync>"
1574 }
1576 }
1575 error: null
1577 error: null
1576
1578
1577 """
1579 """
1578 repo = get_repo_or_error(repoid)
1580 repo = get_repo_or_error(repoid)
1579 repo_name = repo.repo_name
1581 repo_name = repo.repo_name
1580
1582
1581 _repo = RepoModel().get_by_repo_name(fork_name)
1583 _repo = RepoModel().get_by_repo_name(fork_name)
1582 if _repo:
1584 if _repo:
1583 type_ = 'fork' if _repo.fork else 'repo'
1585 type_ = 'fork' if _repo.fork else 'repo'
1584 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1586 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
1585
1587
1586 if HasPermissionAny('hg.admin')():
1588 if HasPermissionAny('hg.admin')():
1587 pass
1589 pass
1588 elif HasRepoPermissionLevel('read')(repo.repo_name):
1590 elif HasRepoPermissionLevel('read')(repo.repo_name):
1589 if not isinstance(owner, Optional):
1591 if not isinstance(owner, Optional):
1590 # forbid setting owner for non-admins
1592 # forbid setting owner for non-admins
1591 raise JSONRPCError(
1593 raise JSONRPCError(
1592 'Only Kallithea admin can specify `owner` param'
1594 'Only Kallithea admin can specify `owner` param'
1593 )
1595 )
1594
1596
1595 if not HasPermissionAny('hg.create.repository')():
1597 if not HasPermissionAny('hg.create.repository')():
1596 raise JSONRPCError('no permission to create repositories')
1598 raise JSONRPCError('no permission to create repositories')
1597 else:
1599 else:
1598 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1600 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1599
1601
1600 if isinstance(owner, Optional):
1602 if isinstance(owner, Optional):
1601 owner = request.authuser.user_id
1603 owner = request.authuser.user_id
1602
1604
1603 owner = get_user_or_error(owner)
1605 owner = get_user_or_error(owner)
1604
1606
1605 try:
1607 try:
1606 # create structure of groups and return the last group
1608 # create structure of groups and return the last group
1607 group = map_groups(fork_name)
1609 group = map_groups(fork_name)
1608 fork_base_name = fork_name.rsplit('/', 1)[-1]
1610 fork_base_name = fork_name.rsplit('/', 1)[-1]
1609
1611
1610 form_data = dict(
1612 form_data = dict(
1611 repo_name=fork_base_name,
1613 repo_name=fork_base_name,
1612 repo_name_full=fork_name,
1614 repo_name_full=fork_name,
1613 repo_group=group,
1615 repo_group=group,
1614 repo_type=repo.repo_type,
1616 repo_type=repo.repo_type,
1615 description=Optional.extract(description),
1617 description=Optional.extract(description),
1616 private=Optional.extract(private),
1618 private=Optional.extract(private),
1617 copy_permissions=Optional.extract(copy_permissions),
1619 copy_permissions=Optional.extract(copy_permissions),
1618 landing_rev=Optional.extract(landing_rev),
1620 landing_rev=Optional.extract(landing_rev),
1619 update_after_clone=False,
1621 update_after_clone=False,
1620 fork_parent_id=repo.repo_id,
1622 fork_parent_id=repo.repo_id,
1621 )
1623 )
1622 task = RepoModel().create_fork(form_data, cur_user=owner)
1624 task = RepoModel().create_fork(form_data, cur_user=owner)
1623 # no commit, it's done in RepoModel, or async via celery
1625 # no commit, it's done in RepoModel, or async via celery
1624 task_id = task.task_id
1626 task_id = task.task_id
1625 return dict(
1627 return dict(
1626 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
1628 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
1627 fork_name),
1629 fork_name),
1628 success=True, # cannot return the repo data here since fork
1630 success=True, # cannot return the repo data here since fork
1629 # can be done async
1631 # can be done async
1630 task=task_id
1632 task=task_id
1631 )
1633 )
1632 except Exception:
1634 except Exception:
1633 log.error(traceback.format_exc())
1635 log.error(traceback.format_exc())
1634 raise JSONRPCError(
1636 raise JSONRPCError(
1635 'failed to fork repository `%s` as `%s`' % (repo_name,
1637 'failed to fork repository `%s` as `%s`' % (repo_name,
1636 fork_name)
1638 fork_name)
1637 )
1639 )
1638
1640
1639 # permission check inside
1641 # permission check inside
1640 def delete_repo(self, repoid, forks=Optional('')):
1642 def delete_repo(self, repoid, forks=Optional('')):
1641 """
1643 """
1642 Deletes a repository. This command can be executed only using api_key belonging
1644 Deletes a repository. This command can be executed only using api_key belonging
1643 to user with admin rights or regular user that have admin access to repository.
1645 to user with admin rights or regular user that have admin access to repository.
1644 When `forks` param is set it's possible to detach or delete forks of deleting
1646 When `forks` param is set it's possible to detach or delete forks of deleting
1645 repository
1647 repository
1646
1648
1647 :param repoid: repository name or repository id
1649 :param repoid: repository name or repository id
1648 :type repoid: str or int
1650 :type repoid: str or int
1649 :param forks: `detach` or `delete`, what do do with attached forks for repo
1651 :param forks: `detach` or `delete`, what do do with attached forks for repo
1650 :type forks: Optional(str)
1652 :type forks: Optional(str)
1651
1653
1652 OUTPUT::
1654 OUTPUT::
1653
1655
1654 id : <id_given_in_input>
1656 id : <id_given_in_input>
1655 result: {
1657 result: {
1656 "msg": "Deleted repository `<reponame>`",
1658 "msg": "Deleted repository `<reponame>`",
1657 "success": true
1659 "success": true
1658 }
1660 }
1659 error: null
1661 error: null
1660
1662
1661 """
1663 """
1662 repo = get_repo_or_error(repoid)
1664 repo = get_repo_or_error(repoid)
1663
1665
1664 if not HasPermissionAny('hg.admin')():
1666 if not HasPermissionAny('hg.admin')():
1665 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1667 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1666 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1668 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1667
1669
1668 try:
1670 try:
1669 handle_forks = Optional.extract(forks)
1671 handle_forks = Optional.extract(forks)
1670 _forks_msg = ''
1672 _forks_msg = ''
1671 _forks = [f for f in repo.forks]
1673 _forks = [f for f in repo.forks]
1672 if handle_forks == 'detach':
1674 if handle_forks == 'detach':
1673 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1675 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1674 elif handle_forks == 'delete':
1676 elif handle_forks == 'delete':
1675 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1677 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1676 elif _forks:
1678 elif _forks:
1677 raise JSONRPCError(
1679 raise JSONRPCError(
1678 'Cannot delete `%s` it still contains attached forks' %
1680 'Cannot delete `%s` it still contains attached forks' %
1679 (repo.repo_name,)
1681 (repo.repo_name,)
1680 )
1682 )
1681
1683
1682 RepoModel().delete(repo, forks=forks)
1684 RepoModel().delete(repo, forks=forks)
1683 Session().commit()
1685 Session().commit()
1684 return dict(
1686 return dict(
1685 msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
1687 msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
1686 success=True
1688 success=True
1687 )
1689 )
1688 except Exception:
1690 except Exception:
1689 log.error(traceback.format_exc())
1691 log.error(traceback.format_exc())
1690 raise JSONRPCError(
1692 raise JSONRPCError(
1691 'failed to delete repository `%s`' % (repo.repo_name,)
1693 'failed to delete repository `%s`' % (repo.repo_name,)
1692 )
1694 )
1693
1695
1694 @HasPermissionAnyDecorator('hg.admin')
1696 @HasPermissionAnyDecorator('hg.admin')
1695 def grant_user_permission(self, repoid, userid, perm):
1697 def grant_user_permission(self, repoid, userid, perm):
1696 """
1698 """
1697 Grant permission for user on given repository, or update existing one
1699 Grant permission for user on given repository, or update existing one
1698 if found. This command can be executed only using api_key belonging to user
1700 if found. This command can be executed only using api_key belonging to user
1699 with admin rights.
1701 with admin rights.
1700
1702
1701 :param repoid: repository name or repository id
1703 :param repoid: repository name or repository id
1702 :type repoid: str or int
1704 :type repoid: str or int
1703 :param userid:
1705 :param userid:
1704 :param perm: (repository.(none|read|write|admin))
1706 :param perm: (repository.(none|read|write|admin))
1705 :type perm: str
1707 :type perm: str
1706
1708
1707 OUTPUT::
1709 OUTPUT::
1708
1710
1709 id : <id_given_in_input>
1711 id : <id_given_in_input>
1710 result: {
1712 result: {
1711 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1713 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1712 "success": true
1714 "success": true
1713 }
1715 }
1714 error: null
1716 error: null
1715 """
1717 """
1716 repo = get_repo_or_error(repoid)
1718 repo = get_repo_or_error(repoid)
1717 user = get_user_or_error(userid)
1719 user = get_user_or_error(userid)
1718 perm = get_perm_or_error(perm)
1720 perm = get_perm_or_error(perm)
1719
1721
1720 try:
1722 try:
1721
1723
1722 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1724 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1723
1725
1724 Session().commit()
1726 Session().commit()
1725 return dict(
1727 return dict(
1726 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1728 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1727 perm.permission_name, user.username, repo.repo_name
1729 perm.permission_name, user.username, repo.repo_name
1728 ),
1730 ),
1729 success=True
1731 success=True
1730 )
1732 )
1731 except Exception:
1733 except Exception:
1732 log.error(traceback.format_exc())
1734 log.error(traceback.format_exc())
1733 raise JSONRPCError(
1735 raise JSONRPCError(
1734 'failed to edit permission for user: `%s` in repo: `%s`' % (
1736 'failed to edit permission for user: `%s` in repo: `%s`' % (
1735 userid, repoid
1737 userid, repoid
1736 )
1738 )
1737 )
1739 )
1738
1740
1739 @HasPermissionAnyDecorator('hg.admin')
1741 @HasPermissionAnyDecorator('hg.admin')
1740 def revoke_user_permission(self, repoid, userid):
1742 def revoke_user_permission(self, repoid, userid):
1741 """
1743 """
1742 Revoke permission for user on given repository. This command can be executed
1744 Revoke permission for user on given repository. This command can be executed
1743 only using api_key belonging to user with admin rights.
1745 only using api_key belonging to user with admin rights.
1744
1746
1745 :param repoid: repository name or repository id
1747 :param repoid: repository name or repository id
1746 :type repoid: str or int
1748 :type repoid: str or int
1747 :param userid:
1749 :param userid:
1748
1750
1749 OUTPUT::
1751 OUTPUT::
1750
1752
1751 id : <id_given_in_input>
1753 id : <id_given_in_input>
1752 result: {
1754 result: {
1753 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1755 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1754 "success": true
1756 "success": true
1755 }
1757 }
1756 error: null
1758 error: null
1757
1759
1758 """
1760 """
1759
1761
1760 repo = get_repo_or_error(repoid)
1762 repo = get_repo_or_error(repoid)
1761 user = get_user_or_error(userid)
1763 user = get_user_or_error(userid)
1762 try:
1764 try:
1763 RepoModel().revoke_user_permission(repo=repo, user=user)
1765 RepoModel().revoke_user_permission(repo=repo, user=user)
1764 Session().commit()
1766 Session().commit()
1765 return dict(
1767 return dict(
1766 msg='Revoked perm for user: `%s` in repo: `%s`' % (
1768 msg='Revoked perm for user: `%s` in repo: `%s`' % (
1767 user.username, repo.repo_name
1769 user.username, repo.repo_name
1768 ),
1770 ),
1769 success=True
1771 success=True
1770 )
1772 )
1771 except Exception:
1773 except Exception:
1772 log.error(traceback.format_exc())
1774 log.error(traceback.format_exc())
1773 raise JSONRPCError(
1775 raise JSONRPCError(
1774 'failed to edit permission for user: `%s` in repo: `%s`' % (
1776 'failed to edit permission for user: `%s` in repo: `%s`' % (
1775 userid, repoid
1777 userid, repoid
1776 )
1778 )
1777 )
1779 )
1778
1780
1779 # permission check inside
1781 # permission check inside
1780 def grant_user_group_permission(self, repoid, usergroupid, perm):
1782 def grant_user_group_permission(self, repoid, usergroupid, perm):
1781 """
1783 """
1782 Grant permission for user group on given repository, or update
1784 Grant permission for user group on given repository, or update
1783 existing one if found. This command can be executed only using
1785 existing one if found. This command can be executed only using
1784 api_key belonging to user with admin rights.
1786 api_key belonging to user with admin rights.
1785
1787
1786 :param repoid: repository name or repository id
1788 :param repoid: repository name or repository id
1787 :type repoid: str or int
1789 :type repoid: str or int
1788 :param usergroupid: id of usergroup
1790 :param usergroupid: id of usergroup
1789 :type usergroupid: str or int
1791 :type usergroupid: str or int
1790 :param perm: (repository.(none|read|write|admin))
1792 :param perm: (repository.(none|read|write|admin))
1791 :type perm: str
1793 :type perm: str
1792
1794
1793 OUTPUT::
1795 OUTPUT::
1794
1796
1795 id : <id_given_in_input>
1797 id : <id_given_in_input>
1796 result : {
1798 result : {
1797 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1799 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1798 "success": true
1800 "success": true
1799
1801
1800 }
1802 }
1801 error : null
1803 error : null
1802
1804
1803 ERROR OUTPUT::
1805 ERROR OUTPUT::
1804
1806
1805 id : <id_given_in_input>
1807 id : <id_given_in_input>
1806 result : null
1808 result : null
1807 error : {
1809 error : {
1808 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1810 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1809 }
1811 }
1810
1812
1811 """
1813 """
1812 repo = get_repo_or_error(repoid)
1814 repo = get_repo_or_error(repoid)
1813 perm = get_perm_or_error(perm)
1815 perm = get_perm_or_error(perm)
1814 user_group = get_user_group_or_error(usergroupid)
1816 user_group = get_user_group_or_error(usergroupid)
1815 if not HasPermissionAny('hg.admin')():
1817 if not HasPermissionAny('hg.admin')():
1816 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1818 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1817 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1819 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1818
1820
1819 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1821 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1820 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1822 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1821
1823
1822 try:
1824 try:
1823 RepoModel().grant_user_group_permission(
1825 RepoModel().grant_user_group_permission(
1824 repo=repo, group_name=user_group, perm=perm)
1826 repo=repo, group_name=user_group, perm=perm)
1825
1827
1826 Session().commit()
1828 Session().commit()
1827 return dict(
1829 return dict(
1828 msg='Granted perm: `%s` for user group: `%s` in '
1830 msg='Granted perm: `%s` for user group: `%s` in '
1829 'repo: `%s`' % (
1831 'repo: `%s`' % (
1830 perm.permission_name, user_group.users_group_name,
1832 perm.permission_name, user_group.users_group_name,
1831 repo.repo_name
1833 repo.repo_name
1832 ),
1834 ),
1833 success=True
1835 success=True
1834 )
1836 )
1835 except Exception:
1837 except Exception:
1836 log.error(traceback.format_exc())
1838 log.error(traceback.format_exc())
1837 raise JSONRPCError(
1839 raise JSONRPCError(
1838 'failed to edit permission for user group: `%s` in '
1840 'failed to edit permission for user group: `%s` in '
1839 'repo: `%s`' % (
1841 'repo: `%s`' % (
1840 usergroupid, repo.repo_name
1842 usergroupid, repo.repo_name
1841 )
1843 )
1842 )
1844 )
1843
1845
1844 # permission check inside
1846 # permission check inside
1845 def revoke_user_group_permission(self, repoid, usergroupid):
1847 def revoke_user_group_permission(self, repoid, usergroupid):
1846 """
1848 """
1847 Revoke permission for user group on given repository. This command can be
1849 Revoke permission for user group on given repository. This command can be
1848 executed only using api_key belonging to user with admin rights.
1850 executed only using api_key belonging to user with admin rights.
1849
1851
1850 :param repoid: repository name or repository id
1852 :param repoid: repository name or repository id
1851 :type repoid: str or int
1853 :type repoid: str or int
1852 :param usergroupid:
1854 :param usergroupid:
1853
1855
1854 OUTPUT::
1856 OUTPUT::
1855
1857
1856 id : <id_given_in_input>
1858 id : <id_given_in_input>
1857 result: {
1859 result: {
1858 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1860 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1859 "success": true
1861 "success": true
1860 }
1862 }
1861 error: null
1863 error: null
1862 """
1864 """
1863 repo = get_repo_or_error(repoid)
1865 repo = get_repo_or_error(repoid)
1864 user_group = get_user_group_or_error(usergroupid)
1866 user_group = get_user_group_or_error(usergroupid)
1865 if not HasPermissionAny('hg.admin')():
1867 if not HasPermissionAny('hg.admin')():
1866 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1868 if not HasRepoPermissionLevel('admin')(repo.repo_name):
1867 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1869 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
1868
1870
1869 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1871 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
1870 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1872 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
1871
1873
1872 try:
1874 try:
1873 RepoModel().revoke_user_group_permission(
1875 RepoModel().revoke_user_group_permission(
1874 repo=repo, group_name=user_group)
1876 repo=repo, group_name=user_group)
1875
1877
1876 Session().commit()
1878 Session().commit()
1877 return dict(
1879 return dict(
1878 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
1880 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
1879 user_group.users_group_name, repo.repo_name
1881 user_group.users_group_name, repo.repo_name
1880 ),
1882 ),
1881 success=True
1883 success=True
1882 )
1884 )
1883 except Exception:
1885 except Exception:
1884 log.error(traceback.format_exc())
1886 log.error(traceback.format_exc())
1885 raise JSONRPCError(
1887 raise JSONRPCError(
1886 'failed to edit permission for user group: `%s` in '
1888 'failed to edit permission for user group: `%s` in '
1887 'repo: `%s`' % (
1889 'repo: `%s`' % (
1888 user_group.users_group_name, repo.repo_name
1890 user_group.users_group_name, repo.repo_name
1889 )
1891 )
1890 )
1892 )
1891
1893
1892 @HasPermissionAnyDecorator('hg.admin')
1894 @HasPermissionAnyDecorator('hg.admin')
1893 def get_repo_group(self, repogroupid):
1895 def get_repo_group(self, repogroupid):
1894 """
1896 """
1895 Returns given repo group together with permissions, and repositories
1897 Returns given repo group together with permissions, and repositories
1896 inside the group
1898 inside the group
1897
1899
1898 :param repogroupid: id/name of repository group
1900 :param repogroupid: id/name of repository group
1899 :type repogroupid: str or int
1901 :type repogroupid: str or int
1900 """
1902 """
1901 repo_group = get_repo_group_or_error(repogroupid)
1903 repo_group = get_repo_group_or_error(repogroupid)
1902
1904
1903 members = []
1905 members = []
1904 for user in repo_group.repo_group_to_perm:
1906 for user in repo_group.repo_group_to_perm:
1905 perm = user.permission.permission_name
1907 perm = user.permission.permission_name
1906 user = user.user
1908 user = user.user
1907 user_data = {
1909 user_data = {
1908 'name': user.username,
1910 'name': user.username,
1909 'type': "user",
1911 'type': "user",
1910 'permission': perm
1912 'permission': perm
1911 }
1913 }
1912 members.append(user_data)
1914 members.append(user_data)
1913
1915
1914 for user_group in repo_group.users_group_to_perm:
1916 for user_group in repo_group.users_group_to_perm:
1915 perm = user_group.permission.permission_name
1917 perm = user_group.permission.permission_name
1916 user_group = user_group.users_group
1918 user_group = user_group.users_group
1917 user_group_data = {
1919 user_group_data = {
1918 'name': user_group.users_group_name,
1920 'name': user_group.users_group_name,
1919 'type': "user_group",
1921 'type': "user_group",
1920 'permission': perm
1922 'permission': perm
1921 }
1923 }
1922 members.append(user_group_data)
1924 members.append(user_group_data)
1923
1925
1924 data = repo_group.get_api_data()
1926 data = repo_group.get_api_data()
1925 data["members"] = members
1927 data["members"] = members
1926 return data
1928 return data
1927
1929
1928 @HasPermissionAnyDecorator('hg.admin')
1930 @HasPermissionAnyDecorator('hg.admin')
1929 def get_repo_groups(self):
1931 def get_repo_groups(self):
1930 """
1932 """
1931 Returns all repository groups
1933 Returns all repository groups
1932
1934
1933 """
1935 """
1934 return [
1936 return [
1935 repo_group.get_api_data()
1937 repo_group.get_api_data()
1936 for repo_group in RepoGroup.query()
1938 for repo_group in RepoGroup.query()
1937 ]
1939 ]
1938
1940
1939 @HasPermissionAnyDecorator('hg.admin')
1941 @HasPermissionAnyDecorator('hg.admin')
1940 def create_repo_group(self, group_name, description=Optional(''),
1942 def create_repo_group(self, group_name, description=Optional(''),
1941 owner=Optional(OAttr('apiuser')),
1943 owner=Optional(OAttr('apiuser')),
1942 parent=Optional(None),
1944 parent=Optional(None),
1943 copy_permissions=Optional(False)):
1945 copy_permissions=Optional(False)):
1944 """
1946 """
1945 Creates a repository group. This command can be executed only using
1947 Creates a repository group. This command can be executed only using
1946 api_key belonging to user with admin rights.
1948 api_key belonging to user with admin rights.
1947
1949
1948 :param group_name:
1950 :param group_name:
1949 :type group_name:
1951 :type group_name:
1950 :param description:
1952 :param description:
1951 :type description:
1953 :type description:
1952 :param owner:
1954 :param owner:
1953 :type owner:
1955 :type owner:
1954 :param parent:
1956 :param parent:
1955 :type parent:
1957 :type parent:
1956 :param copy_permissions:
1958 :param copy_permissions:
1957 :type copy_permissions:
1959 :type copy_permissions:
1958
1960
1959 OUTPUT::
1961 OUTPUT::
1960
1962
1961 id : <id_given_in_input>
1963 id : <id_given_in_input>
1962 result : {
1964 result : {
1963 "msg": "created new repo group `<repo_group_name>`"
1965 "msg": "created new repo group `<repo_group_name>`"
1964 "repo_group": <repogroup_object>
1966 "repo_group": <repogroup_object>
1965 }
1967 }
1966 error : null
1968 error : null
1967
1969
1968 ERROR OUTPUT::
1970 ERROR OUTPUT::
1969
1971
1970 id : <id_given_in_input>
1972 id : <id_given_in_input>
1971 result : null
1973 result : null
1972 error : {
1974 error : {
1973 failed to create repo group `<repogroupid>`
1975 failed to create repo group `<repogroupid>`
1974 }
1976 }
1975
1977
1976 """
1978 """
1977 if RepoGroup.get_by_group_name(group_name):
1979 if RepoGroup.get_by_group_name(group_name):
1978 raise JSONRPCError("repo group `%s` already exist" % (group_name,))
1980 raise JSONRPCError("repo group `%s` already exist" % (group_name,))
1979
1981
1980 if isinstance(owner, Optional):
1982 if isinstance(owner, Optional):
1981 owner = request.authuser.user_id
1983 owner = request.authuser.user_id
1982 group_description = Optional.extract(description)
1984 group_description = Optional.extract(description)
1983 parent_group = Optional.extract(parent)
1985 parent_group = Optional.extract(parent)
1984 if not isinstance(parent, Optional):
1986 if not isinstance(parent, Optional):
1985 parent_group = get_repo_group_or_error(parent_group)
1987 parent_group = get_repo_group_or_error(parent_group)
1986
1988
1987 copy_permissions = Optional.extract(copy_permissions)
1989 copy_permissions = Optional.extract(copy_permissions)
1988 try:
1990 try:
1989 repo_group = RepoGroupModel().create(
1991 repo_group = RepoGroupModel().create(
1990 group_name=group_name,
1992 group_name=group_name,
1991 group_description=group_description,
1993 group_description=group_description,
1992 owner=owner,
1994 owner=owner,
1993 parent=parent_group,
1995 parent=parent_group,
1994 copy_permissions=copy_permissions
1996 copy_permissions=copy_permissions
1995 )
1997 )
1996 Session().commit()
1998 Session().commit()
1997 return dict(
1999 return dict(
1998 msg='created new repo group `%s`' % group_name,
2000 msg='created new repo group `%s`' % group_name,
1999 repo_group=repo_group.get_api_data()
2001 repo_group=repo_group.get_api_data()
2000 )
2002 )
2001 except Exception:
2003 except Exception:
2002
2004
2003 log.error(traceback.format_exc())
2005 log.error(traceback.format_exc())
2004 raise JSONRPCError('failed to create repo group `%s`' % (group_name,))
2006 raise JSONRPCError('failed to create repo group `%s`' % (group_name,))
2005
2007
2006 @HasPermissionAnyDecorator('hg.admin')
2008 @HasPermissionAnyDecorator('hg.admin')
2007 def update_repo_group(self, repogroupid, group_name=Optional(''),
2009 def update_repo_group(self, repogroupid, group_name=Optional(''),
2008 description=Optional(''),
2010 description=Optional(''),
2009 owner=Optional(OAttr('apiuser')),
2011 owner=Optional(OAttr('apiuser')),
2010 parent=Optional(None), enable_locking=Optional(False)):
2012 parent=Optional(None), enable_locking=Optional(False)):
2011 repo_group = get_repo_group_or_error(repogroupid)
2013 repo_group = get_repo_group_or_error(repogroupid)
2012
2014
2013 updates = {}
2015 updates = {}
2014 try:
2016 try:
2015 store_update(updates, group_name, 'group_name')
2017 store_update(updates, group_name, 'group_name')
2016 store_update(updates, description, 'group_description')
2018 store_update(updates, description, 'group_description')
2017 store_update(updates, owner, 'owner')
2019 store_update(updates, owner, 'owner')
2018 store_update(updates, parent, 'parent_group')
2020 store_update(updates, parent, 'parent_group')
2019 store_update(updates, enable_locking, 'enable_locking')
2021 store_update(updates, enable_locking, 'enable_locking')
2020 repo_group = RepoGroupModel().update(repo_group, updates)
2022 repo_group = RepoGroupModel().update(repo_group, updates)
2021 Session().commit()
2023 Session().commit()
2022 return dict(
2024 return dict(
2023 msg='updated repository group ID:%s %s' % (repo_group.group_id,
2025 msg='updated repository group ID:%s %s' % (repo_group.group_id,
2024 repo_group.group_name),
2026 repo_group.group_name),
2025 repo_group=repo_group.get_api_data()
2027 repo_group=repo_group.get_api_data()
2026 )
2028 )
2027 except Exception:
2029 except Exception:
2028 log.error(traceback.format_exc())
2030 log.error(traceback.format_exc())
2029 raise JSONRPCError('failed to update repository group `%s`'
2031 raise JSONRPCError('failed to update repository group `%s`'
2030 % (repogroupid,))
2032 % (repogroupid,))
2031
2033
2032 @HasPermissionAnyDecorator('hg.admin')
2034 @HasPermissionAnyDecorator('hg.admin')
2033 def delete_repo_group(self, repogroupid):
2035 def delete_repo_group(self, repogroupid):
2034 """
2036 """
2035
2037
2036 :param repogroupid: name or id of repository group
2038 :param repogroupid: name or id of repository group
2037 :type repogroupid: str or int
2039 :type repogroupid: str or int
2038
2040
2039 OUTPUT::
2041 OUTPUT::
2040
2042
2041 id : <id_given_in_input>
2043 id : <id_given_in_input>
2042 result : {
2044 result : {
2043 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
2045 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
2044 'repo_group': null
2046 'repo_group': null
2045 }
2047 }
2046 error : null
2048 error : null
2047
2049
2048 ERROR OUTPUT::
2050 ERROR OUTPUT::
2049
2051
2050 id : <id_given_in_input>
2052 id : <id_given_in_input>
2051 result : null
2053 result : null
2052 error : {
2054 error : {
2053 "failed to delete repo group ID:<repogroupid> <repogroupname>"
2055 "failed to delete repo group ID:<repogroupid> <repogroupname>"
2054 }
2056 }
2055
2057
2056 """
2058 """
2057 repo_group = get_repo_group_or_error(repogroupid)
2059 repo_group = get_repo_group_or_error(repogroupid)
2058
2060
2059 try:
2061 try:
2060 RepoGroupModel().delete(repo_group)
2062 RepoGroupModel().delete(repo_group)
2061 Session().commit()
2063 Session().commit()
2062 return dict(
2064 return dict(
2063 msg='deleted repo group ID:%s %s' %
2065 msg='deleted repo group ID:%s %s' %
2064 (repo_group.group_id, repo_group.group_name),
2066 (repo_group.group_id, repo_group.group_name),
2065 repo_group=None
2067 repo_group=None
2066 )
2068 )
2067 except Exception:
2069 except Exception:
2068 log.error(traceback.format_exc())
2070 log.error(traceback.format_exc())
2069 raise JSONRPCError('failed to delete repo group ID:%s %s' %
2071 raise JSONRPCError('failed to delete repo group ID:%s %s' %
2070 (repo_group.group_id, repo_group.group_name)
2072 (repo_group.group_id, repo_group.group_name)
2071 )
2073 )
2072
2074
2073 # permission check inside
2075 # permission check inside
2074 def grant_user_permission_to_repo_group(self, repogroupid, userid,
2076 def grant_user_permission_to_repo_group(self, repogroupid, userid,
2075 perm, apply_to_children=Optional('none')):
2077 perm, apply_to_children=Optional('none')):
2076 """
2078 """
2077 Grant permission for user on given repository group, or update existing
2079 Grant permission for user on given repository group, or update existing
2078 one if found. This command can be executed only using api_key belonging
2080 one if found. This command can be executed only using api_key belonging
2079 to user with admin rights, or user who has admin right to given repository
2081 to user with admin rights, or user who has admin right to given repository
2080 group.
2082 group.
2081
2083
2082 :param repogroupid: name or id of repository group
2084 :param repogroupid: name or id of repository group
2083 :type repogroupid: str or int
2085 :type repogroupid: str or int
2084 :param userid:
2086 :param userid:
2085 :param perm: (group.(none|read|write|admin))
2087 :param perm: (group.(none|read|write|admin))
2086 :type perm: str
2088 :type perm: str
2087 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2089 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2088 :type apply_to_children: str
2090 :type apply_to_children: str
2089
2091
2090 OUTPUT::
2092 OUTPUT::
2091
2093
2092 id : <id_given_in_input>
2094 id : <id_given_in_input>
2093 result: {
2095 result: {
2094 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2096 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2095 "success": true
2097 "success": true
2096 }
2098 }
2097 error: null
2099 error: null
2098
2100
2099 ERROR OUTPUT::
2101 ERROR OUTPUT::
2100
2102
2101 id : <id_given_in_input>
2103 id : <id_given_in_input>
2102 result : null
2104 result : null
2103 error : {
2105 error : {
2104 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2106 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2105 }
2107 }
2106
2108
2107 """
2109 """
2108
2110
2109 repo_group = get_repo_group_or_error(repogroupid)
2111 repo_group = get_repo_group_or_error(repogroupid)
2110
2112
2111 if not HasPermissionAny('hg.admin')():
2113 if not HasPermissionAny('hg.admin')():
2112 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2114 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2113 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2115 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2114
2116
2115 user = get_user_or_error(userid)
2117 user = get_user_or_error(userid)
2116 perm = get_perm_or_error(perm, prefix='group.')
2118 perm = get_perm_or_error(perm, prefix='group.')
2117 apply_to_children = Optional.extract(apply_to_children)
2119 apply_to_children = Optional.extract(apply_to_children)
2118
2120
2119 try:
2121 try:
2120 RepoGroupModel().add_permission(repo_group=repo_group,
2122 RepoGroupModel().add_permission(repo_group=repo_group,
2121 obj=user,
2123 obj=user,
2122 obj_type="user",
2124 obj_type="user",
2123 perm=perm,
2125 perm=perm,
2124 recursive=apply_to_children)
2126 recursive=apply_to_children)
2125 Session().commit()
2127 Session().commit()
2126 return dict(
2128 return dict(
2127 msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
2129 msg='Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
2128 perm.permission_name, apply_to_children, user.username, repo_group.name
2130 perm.permission_name, apply_to_children, user.username, repo_group.name
2129 ),
2131 ),
2130 success=True
2132 success=True
2131 )
2133 )
2132 except Exception:
2134 except Exception:
2133 log.error(traceback.format_exc())
2135 log.error(traceback.format_exc())
2134 raise JSONRPCError(
2136 raise JSONRPCError(
2135 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2137 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2136 userid, repo_group.name))
2138 userid, repo_group.name))
2137
2139
2138 # permission check inside
2140 # permission check inside
2139 def revoke_user_permission_from_repo_group(self, repogroupid, userid,
2141 def revoke_user_permission_from_repo_group(self, repogroupid, userid,
2140 apply_to_children=Optional('none')):
2142 apply_to_children=Optional('none')):
2141 """
2143 """
2142 Revoke permission for user on given repository group. This command can
2144 Revoke permission for user on given repository group. This command can
2143 be executed only using api_key belonging to user with admin rights, or
2145 be executed only using api_key belonging to user with admin rights, or
2144 user who has admin right to given repository group.
2146 user who has admin right to given repository group.
2145
2147
2146 :param repogroupid: name or id of repository group
2148 :param repogroupid: name or id of repository group
2147 :type repogroupid: str or int
2149 :type repogroupid: str or int
2148 :param userid:
2150 :param userid:
2149 :type userid:
2151 :type userid:
2150 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2152 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2151 :type apply_to_children: str
2153 :type apply_to_children: str
2152
2154
2153 OUTPUT::
2155 OUTPUT::
2154
2156
2155 id : <id_given_in_input>
2157 id : <id_given_in_input>
2156 result: {
2158 result: {
2157 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2159 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2158 "success": true
2160 "success": true
2159 }
2161 }
2160 error: null
2162 error: null
2161
2163
2162 ERROR OUTPUT::
2164 ERROR OUTPUT::
2163
2165
2164 id : <id_given_in_input>
2166 id : <id_given_in_input>
2165 result : null
2167 result : null
2166 error : {
2168 error : {
2167 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2169 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2168 }
2170 }
2169
2171
2170 """
2172 """
2171
2173
2172 repo_group = get_repo_group_or_error(repogroupid)
2174 repo_group = get_repo_group_or_error(repogroupid)
2173
2175
2174 if not HasPermissionAny('hg.admin')():
2176 if not HasPermissionAny('hg.admin')():
2175 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2177 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2176 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2178 raise JSONRPCError('repository group `%s` does not exist' % (repogroupid,))
2177
2179
2178 user = get_user_or_error(userid)
2180 user = get_user_or_error(userid)
2179 apply_to_children = Optional.extract(apply_to_children)
2181 apply_to_children = Optional.extract(apply_to_children)
2180
2182
2181 try:
2183 try:
2182 RepoGroupModel().delete_permission(repo_group=repo_group,
2184 RepoGroupModel().delete_permission(repo_group=repo_group,
2183 obj=user,
2185 obj=user,
2184 obj_type="user",
2186 obj_type="user",
2185 recursive=apply_to_children)
2187 recursive=apply_to_children)
2186
2188
2187 Session().commit()
2189 Session().commit()
2188 return dict(
2190 return dict(
2189 msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2191 msg='Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2190 apply_to_children, user.username, repo_group.name
2192 apply_to_children, user.username, repo_group.name
2191 ),
2193 ),
2192 success=True
2194 success=True
2193 )
2195 )
2194 except Exception:
2196 except Exception:
2195 log.error(traceback.format_exc())
2197 log.error(traceback.format_exc())
2196 raise JSONRPCError(
2198 raise JSONRPCError(
2197 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2199 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2198 userid, repo_group.name))
2200 userid, repo_group.name))
2199
2201
2200 # permission check inside
2202 # permission check inside
2201 def grant_user_group_permission_to_repo_group(
2203 def grant_user_group_permission_to_repo_group(
2202 self, repogroupid, usergroupid, perm,
2204 self, repogroupid, usergroupid, perm,
2203 apply_to_children=Optional('none')):
2205 apply_to_children=Optional('none')):
2204 """
2206 """
2205 Grant permission for user group on given repository group, or update
2207 Grant permission for user group on given repository group, or update
2206 existing one if found. This command can be executed only using
2208 existing one if found. This command can be executed only using
2207 api_key belonging to user with admin rights, or user who has admin
2209 api_key belonging to user with admin rights, or user who has admin
2208 right to given repository group.
2210 right to given repository group.
2209
2211
2210 :param repogroupid: name or id of repository group
2212 :param repogroupid: name or id of repository group
2211 :type repogroupid: str or int
2213 :type repogroupid: str or int
2212 :param usergroupid: id of usergroup
2214 :param usergroupid: id of usergroup
2213 :type usergroupid: str or int
2215 :type usergroupid: str or int
2214 :param perm: (group.(none|read|write|admin))
2216 :param perm: (group.(none|read|write|admin))
2215 :type perm: str
2217 :type perm: str
2216 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2218 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2217 :type apply_to_children: str
2219 :type apply_to_children: str
2218
2220
2219 OUTPUT::
2221 OUTPUT::
2220
2222
2221 id : <id_given_in_input>
2223 id : <id_given_in_input>
2222 result : {
2224 result : {
2223 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2225 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2224 "success": true
2226 "success": true
2225
2227
2226 }
2228 }
2227 error : null
2229 error : null
2228
2230
2229 ERROR OUTPUT::
2231 ERROR OUTPUT::
2230
2232
2231 id : <id_given_in_input>
2233 id : <id_given_in_input>
2232 result : null
2234 result : null
2233 error : {
2235 error : {
2234 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2236 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2235 }
2237 }
2236
2238
2237 """
2239 """
2238 repo_group = get_repo_group_or_error(repogroupid)
2240 repo_group = get_repo_group_or_error(repogroupid)
2239 perm = get_perm_or_error(perm, prefix='group.')
2241 perm = get_perm_or_error(perm, prefix='group.')
2240 user_group = get_user_group_or_error(usergroupid)
2242 user_group = get_user_group_or_error(usergroupid)
2241 if not HasPermissionAny('hg.admin')():
2243 if not HasPermissionAny('hg.admin')():
2242 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2244 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2243 raise JSONRPCError(
2245 raise JSONRPCError(
2244 'repository group `%s` does not exist' % (repogroupid,))
2246 'repository group `%s` does not exist' % (repogroupid,))
2245
2247
2246 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2248 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2247 raise JSONRPCError(
2249 raise JSONRPCError(
2248 'user group `%s` does not exist' % (usergroupid,))
2250 'user group `%s` does not exist' % (usergroupid,))
2249
2251
2250 apply_to_children = Optional.extract(apply_to_children)
2252 apply_to_children = Optional.extract(apply_to_children)
2251
2253
2252 try:
2254 try:
2253 RepoGroupModel().add_permission(repo_group=repo_group,
2255 RepoGroupModel().add_permission(repo_group=repo_group,
2254 obj=user_group,
2256 obj=user_group,
2255 obj_type="user_group",
2257 obj_type="user_group",
2256 perm=perm,
2258 perm=perm,
2257 recursive=apply_to_children)
2259 recursive=apply_to_children)
2258 Session().commit()
2260 Session().commit()
2259 return dict(
2261 return dict(
2260 msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2262 msg='Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2261 perm.permission_name, apply_to_children,
2263 perm.permission_name, apply_to_children,
2262 user_group.users_group_name, repo_group.name
2264 user_group.users_group_name, repo_group.name
2263 ),
2265 ),
2264 success=True
2266 success=True
2265 )
2267 )
2266 except Exception:
2268 except Exception:
2267 log.error(traceback.format_exc())
2269 log.error(traceback.format_exc())
2268 raise JSONRPCError(
2270 raise JSONRPCError(
2269 'failed to edit permission for user group: `%s` in '
2271 'failed to edit permission for user group: `%s` in '
2270 'repo group: `%s`' % (
2272 'repo group: `%s`' % (
2271 usergroupid, repo_group.name
2273 usergroupid, repo_group.name
2272 )
2274 )
2273 )
2275 )
2274
2276
2275 # permission check inside
2277 # permission check inside
2276 def revoke_user_group_permission_from_repo_group(
2278 def revoke_user_group_permission_from_repo_group(
2277 self, repogroupid, usergroupid,
2279 self, repogroupid, usergroupid,
2278 apply_to_children=Optional('none')):
2280 apply_to_children=Optional('none')):
2279 """
2281 """
2280 Revoke permission for user group on given repository. This command can be
2282 Revoke permission for user group on given repository. This command can be
2281 executed only using api_key belonging to user with admin rights, or
2283 executed only using api_key belonging to user with admin rights, or
2282 user who has admin right to given repository group.
2284 user who has admin right to given repository group.
2283
2285
2284 :param repogroupid: name or id of repository group
2286 :param repogroupid: name or id of repository group
2285 :type repogroupid: str or int
2287 :type repogroupid: str or int
2286 :param usergroupid:
2288 :param usergroupid:
2287 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2289 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2288 :type apply_to_children: str
2290 :type apply_to_children: str
2289
2291
2290 OUTPUT::
2292 OUTPUT::
2291
2293
2292 id : <id_given_in_input>
2294 id : <id_given_in_input>
2293 result: {
2295 result: {
2294 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2296 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2295 "success": true
2297 "success": true
2296 }
2298 }
2297 error: null
2299 error: null
2298
2300
2299 ERROR OUTPUT::
2301 ERROR OUTPUT::
2300
2302
2301 id : <id_given_in_input>
2303 id : <id_given_in_input>
2302 result : null
2304 result : null
2303 error : {
2305 error : {
2304 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2306 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2305 }
2307 }
2306
2308
2307
2309
2308 """
2310 """
2309 repo_group = get_repo_group_or_error(repogroupid)
2311 repo_group = get_repo_group_or_error(repogroupid)
2310 user_group = get_user_group_or_error(usergroupid)
2312 user_group = get_user_group_or_error(usergroupid)
2311 if not HasPermissionAny('hg.admin')():
2313 if not HasPermissionAny('hg.admin')():
2312 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2314 if not HasRepoGroupPermissionLevel('admin')(repo_group.group_name):
2313 raise JSONRPCError(
2315 raise JSONRPCError(
2314 'repository group `%s` does not exist' % (repogroupid,))
2316 'repository group `%s` does not exist' % (repogroupid,))
2315
2317
2316 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2318 if not HasUserGroupPermissionLevel('read')(user_group.users_group_name):
2317 raise JSONRPCError(
2319 raise JSONRPCError(
2318 'user group `%s` does not exist' % (usergroupid,))
2320 'user group `%s` does not exist' % (usergroupid,))
2319
2321
2320 apply_to_children = Optional.extract(apply_to_children)
2322 apply_to_children = Optional.extract(apply_to_children)
2321
2323
2322 try:
2324 try:
2323 RepoGroupModel().delete_permission(repo_group=repo_group,
2325 RepoGroupModel().delete_permission(repo_group=repo_group,
2324 obj=user_group,
2326 obj=user_group,
2325 obj_type="user_group",
2327 obj_type="user_group",
2326 recursive=apply_to_children)
2328 recursive=apply_to_children)
2327 Session().commit()
2329 Session().commit()
2328 return dict(
2330 return dict(
2329 msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2331 msg='Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2330 apply_to_children, user_group.users_group_name, repo_group.name
2332 apply_to_children, user_group.users_group_name, repo_group.name
2331 ),
2333 ),
2332 success=True
2334 success=True
2333 )
2335 )
2334 except Exception:
2336 except Exception:
2335 log.error(traceback.format_exc())
2337 log.error(traceback.format_exc())
2336 raise JSONRPCError(
2338 raise JSONRPCError(
2337 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2339 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2338 user_group.users_group_name, repo_group.name
2340 user_group.users_group_name, repo_group.name
2339 )
2341 )
2340 )
2342 )
2341
2343
2342 def get_gist(self, gistid):
2344 def get_gist(self, gistid):
2343 """
2345 """
2344 Get given gist by id
2346 Get given gist by id
2345
2347
2346 :param gistid: id of private or public gist
2348 :param gistid: id of private or public gist
2347 :type gistid: str
2349 :type gistid: str
2348 """
2350 """
2349 gist = get_gist_or_error(gistid)
2351 gist = get_gist_or_error(gistid)
2350 if not HasPermissionAny('hg.admin')():
2352 if not HasPermissionAny('hg.admin')():
2351 if gist.owner_id != request.authuser.user_id:
2353 if gist.owner_id != request.authuser.user_id:
2352 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2354 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2353 return gist.get_api_data()
2355 return gist.get_api_data()
2354
2356
2355 def get_gists(self, userid=Optional(OAttr('apiuser'))):
2357 def get_gists(self, userid=Optional(OAttr('apiuser'))):
2356 """
2358 """
2357 Get all gists for given user. If userid is empty returned gists
2359 Get all gists for given user. If userid is empty returned gists
2358 are for user who called the api
2360 are for user who called the api
2359
2361
2360 :param userid: user to get gists for
2362 :param userid: user to get gists for
2361 :type userid: Optional(str or int)
2363 :type userid: Optional(str or int)
2362 """
2364 """
2363 if not HasPermissionAny('hg.admin')():
2365 if not HasPermissionAny('hg.admin')():
2364 # make sure normal user does not pass someone else userid,
2366 # make sure normal user does not pass someone else userid,
2365 # he is not allowed to do that
2367 # he is not allowed to do that
2366 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
2368 if not isinstance(userid, Optional) and userid != request.authuser.user_id:
2367 raise JSONRPCError(
2369 raise JSONRPCError(
2368 'userid is not the same as your user'
2370 'userid is not the same as your user'
2369 )
2371 )
2370
2372
2371 if isinstance(userid, Optional):
2373 if isinstance(userid, Optional):
2372 user_id = request.authuser.user_id
2374 user_id = request.authuser.user_id
2373 else:
2375 else:
2374 user_id = get_user_or_error(userid).user_id
2376 user_id = get_user_or_error(userid).user_id
2375
2377
2376 return [
2378 return [
2377 gist.get_api_data()
2379 gist.get_api_data()
2378 for gist in Gist().query()
2380 for gist in Gist().query()
2379 .filter_by(is_expired=False)
2381 .filter_by(is_expired=False)
2380 .filter(Gist.owner_id == user_id)
2382 .filter(Gist.owner_id == user_id)
2381 .order_by(Gist.created_on.desc())
2383 .order_by(Gist.created_on.desc())
2382 ]
2384 ]
2383
2385
2384 def create_gist(self, files, owner=Optional(OAttr('apiuser')),
2386 def create_gist(self, files, owner=Optional(OAttr('apiuser')),
2385 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
2387 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
2386 description=Optional('')):
2388 description=Optional('')):
2387
2389
2388 """
2390 """
2389 Creates new Gist
2391 Creates new Gist
2390
2392
2391 :param files: files to be added to gist
2393 :param files: files to be added to gist
2392 {'filename': {'content':'...', 'lexer': null},
2394 {'filename': {'content':'...', 'lexer': null},
2393 'filename2': {'content':'...', 'lexer': null}}
2395 'filename2': {'content':'...', 'lexer': null}}
2394 :type files: dict
2396 :type files: dict
2395 :param owner: gist owner, defaults to api method caller
2397 :param owner: gist owner, defaults to api method caller
2396 :type owner: Optional(str or int)
2398 :type owner: Optional(str or int)
2397 :param gist_type: type of gist 'public' or 'private'
2399 :param gist_type: type of gist 'public' or 'private'
2398 :type gist_type: Optional(str)
2400 :type gist_type: Optional(str)
2399 :param lifetime: time in minutes of gist lifetime
2401 :param lifetime: time in minutes of gist lifetime
2400 :type lifetime: Optional(int)
2402 :type lifetime: Optional(int)
2401 :param description: gist description
2403 :param description: gist description
2402 :type description: Optional(str)
2404 :type description: Optional(str)
2403
2405
2404 OUTPUT::
2406 OUTPUT::
2405
2407
2406 id : <id_given_in_input>
2408 id : <id_given_in_input>
2407 result : {
2409 result : {
2408 "msg": "created new gist",
2410 "msg": "created new gist",
2409 "gist": {}
2411 "gist": {}
2410 }
2412 }
2411 error : null
2413 error : null
2412
2414
2413 ERROR OUTPUT::
2415 ERROR OUTPUT::
2414
2416
2415 id : <id_given_in_input>
2417 id : <id_given_in_input>
2416 result : null
2418 result : null
2417 error : {
2419 error : {
2418 "failed to create gist"
2420 "failed to create gist"
2419 }
2421 }
2420
2422
2421 """
2423 """
2422 try:
2424 try:
2423 if isinstance(owner, Optional):
2425 if isinstance(owner, Optional):
2424 owner = request.authuser.user_id
2426 owner = request.authuser.user_id
2425
2427
2426 owner = get_user_or_error(owner)
2428 owner = get_user_or_error(owner)
2427 description = Optional.extract(description)
2429 description = Optional.extract(description)
2428 gist_type = Optional.extract(gist_type)
2430 gist_type = Optional.extract(gist_type)
2429 lifetime = Optional.extract(lifetime)
2431 lifetime = Optional.extract(lifetime)
2430
2432
2431 gist = GistModel().create(description=description,
2433 gist = GistModel().create(description=description,
2432 owner=owner,
2434 owner=owner,
2433 gist_mapping=files,
2435 gist_mapping=files,
2434 gist_type=gist_type,
2436 gist_type=gist_type,
2435 lifetime=lifetime)
2437 lifetime=lifetime)
2436 Session().commit()
2438 Session().commit()
2437 return dict(
2439 return dict(
2438 msg='created new gist',
2440 msg='created new gist',
2439 gist=gist.get_api_data()
2441 gist=gist.get_api_data()
2440 )
2442 )
2441 except Exception:
2443 except Exception:
2442 log.error(traceback.format_exc())
2444 log.error(traceback.format_exc())
2443 raise JSONRPCError('failed to create gist')
2445 raise JSONRPCError('failed to create gist')
2444
2446
2445 # def update_gist(self, gistid, files, owner=Optional(OAttr('apiuser')),
2447 # def update_gist(self, gistid, files, owner=Optional(OAttr('apiuser')),
2446 # gist_type=Optional(Gist.GIST_PUBLIC),
2448 # gist_type=Optional(Gist.GIST_PUBLIC),
2447 # gist_lifetime=Optional(-1), gist_description=Optional('')):
2449 # gist_lifetime=Optional(-1), gist_description=Optional('')):
2448 # gist = get_gist_or_error(gistid)
2450 # gist = get_gist_or_error(gistid)
2449 # updates = {}
2451 # updates = {}
2450
2452
2451 # permission check inside
2453 # permission check inside
2452 def delete_gist(self, gistid):
2454 def delete_gist(self, gistid):
2453 """
2455 """
2454 Deletes existing gist
2456 Deletes existing gist
2455
2457
2456 :param gistid: id of gist to delete
2458 :param gistid: id of gist to delete
2457 :type gistid: str
2459 :type gistid: str
2458
2460
2459 OUTPUT::
2461 OUTPUT::
2460
2462
2461 id : <id_given_in_input>
2463 id : <id_given_in_input>
2462 result : {
2464 result : {
2463 "deleted gist ID: <gist_id>",
2465 "deleted gist ID: <gist_id>",
2464 "gist": null
2466 "gist": null
2465 }
2467 }
2466 error : null
2468 error : null
2467
2469
2468 ERROR OUTPUT::
2470 ERROR OUTPUT::
2469
2471
2470 id : <id_given_in_input>
2472 id : <id_given_in_input>
2471 result : null
2473 result : null
2472 error : {
2474 error : {
2473 "failed to delete gist ID:<gist_id>"
2475 "failed to delete gist ID:<gist_id>"
2474 }
2476 }
2475
2477
2476 """
2478 """
2477 gist = get_gist_or_error(gistid)
2479 gist = get_gist_or_error(gistid)
2478 if not HasPermissionAny('hg.admin')():
2480 if not HasPermissionAny('hg.admin')():
2479 if gist.owner_id != request.authuser.user_id:
2481 if gist.owner_id != request.authuser.user_id:
2480 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2482 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
2481
2483
2482 try:
2484 try:
2483 GistModel().delete(gist)
2485 GistModel().delete(gist)
2484 Session().commit()
2486 Session().commit()
2485 return dict(
2487 return dict(
2486 msg='deleted gist ID:%s' % (gist.gist_access_id,),
2488 msg='deleted gist ID:%s' % (gist.gist_access_id,),
2487 gist=None
2489 gist=None
2488 )
2490 )
2489 except Exception:
2491 except Exception:
2490 log.error(traceback.format_exc())
2492 log.error(traceback.format_exc())
2491 raise JSONRPCError('failed to delete gist ID:%s'
2493 raise JSONRPCError('failed to delete gist ID:%s'
2492 % (gist.gist_access_id,))
2494 % (gist.gist_access_id,))
2493
2495
2494 # permission check inside
2496 # permission check inside
2497 def get_changesets(self, repoid, start=None, end=None, start_date=None,
2498 end_date=None, branch_name=None, reverse=False, with_file_list=False):
2499 repo = get_repo_or_error(repoid)
2500 if not HasRepoPermissionLevel('read')(repo.repo_name):
2501 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2502
2503 format = "%Y-%m-%dT%H:%M:%S"
2504 try:
2505 return [e.__json__(with_file_list) for e in
2506 repo.scm_instance.get_changesets(start,
2507 end,
2508 datetime.strptime(start_date, format) if start_date else None,
2509 datetime.strptime(end_date, format) if end_date else None,
2510 branch_name,
2511 reverse)]
2512 except EmptyRepositoryError as e:
2513 raise JSONRPCError(e.message)
2514
2515 # permission check inside
2495 def get_changeset(self, repoid, raw_id, with_reviews=Optional(False)):
2516 def get_changeset(self, repoid, raw_id, with_reviews=Optional(False)):
2496 repo = get_repo_or_error(repoid)
2517 repo = get_repo_or_error(repoid)
2497 if not HasRepoPermissionLevel('read')(repo.repo_name):
2518 if not HasRepoPermissionLevel('read')(repo.repo_name):
2498 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2519 raise JSONRPCError('Access denied to repo %s' % repo.repo_name)
2499 changeset = repo.get_changeset(raw_id)
2520 changeset = repo.get_changeset(raw_id)
2500 if isinstance(changeset, EmptyChangeset):
2521 if isinstance(changeset, EmptyChangeset):
2501 raise JSONRPCError('Changeset %s does not exist' % raw_id)
2522 raise JSONRPCError('Changeset %s does not exist' % raw_id)
2502
2523
2503 info = dict(changeset.as_dict())
2524 info = dict(changeset.as_dict())
2504
2525
2505 with_reviews = Optional.extract(with_reviews)
2526 with_reviews = Optional.extract(with_reviews)
2506 if with_reviews:
2527 if with_reviews:
2507 reviews = ChangesetStatusModel().get_statuses(
2528 reviews = ChangesetStatusModel().get_statuses(
2508 repo.repo_name, raw_id)
2529 repo.repo_name, raw_id)
2509 info["reviews"] = reviews
2530 info["reviews"] = reviews
2510
2531
2511 return info
2532 return info
2512
2533
2513 # permission check inside
2534 # permission check inside
2514 def get_pullrequest(self, pullrequest_id):
2535 def get_pullrequest(self, pullrequest_id):
2515 """
2536 """
2516 Get given pull request by id
2537 Get given pull request by id
2517 """
2538 """
2518 pull_request = PullRequest.get(pullrequest_id)
2539 pull_request = PullRequest.get(pullrequest_id)
2519 if pull_request is None:
2540 if pull_request is None:
2520 raise JSONRPCError('pull request `%s` does not exist' % (pullrequest_id,))
2541 raise JSONRPCError('pull request `%s` does not exist' % (pullrequest_id,))
2521 if not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name):
2542 if not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name):
2522 raise JSONRPCError('not allowed')
2543 raise JSONRPCError('not allowed')
2523 return pull_request.get_api_data()
2544 return pull_request.get_api_data()
2524
2545
2525 # permission check inside
2546 # permission check inside
2526 def comment_pullrequest(self, pull_request_id, comment_msg=u'', status=None, close_pr=False):
2547 def comment_pullrequest(self, pull_request_id, comment_msg=u'', status=None, close_pr=False):
2527 """
2548 """
2528 Add comment, close and change status of pull request.
2549 Add comment, close and change status of pull request.
2529 """
2550 """
2530 apiuser = get_user_or_error(request.authuser.user_id)
2551 apiuser = get_user_or_error(request.authuser.user_id)
2531 pull_request = PullRequest.get(pull_request_id)
2552 pull_request = PullRequest.get(pull_request_id)
2532 if pull_request is None:
2553 if pull_request is None:
2533 raise JSONRPCError('pull request `%s` does not exist' % (pull_request_id,))
2554 raise JSONRPCError('pull request `%s` does not exist' % (pull_request_id,))
2534 if (not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name)):
2555 if (not HasRepoPermissionLevel('read')(pull_request.org_repo.repo_name)):
2535 raise JSONRPCError('No permission to add comment. User needs at least reading permissions'
2556 raise JSONRPCError('No permission to add comment. User needs at least reading permissions'
2536 ' to the source repository.')
2557 ' to the source repository.')
2537 owner = apiuser.user_id == pull_request.owner_id
2558 owner = apiuser.user_id == pull_request.owner_id
2538 reviewer = apiuser.user_id in [reviewer.user_id for reviewer in pull_request.reviewers]
2559 reviewer = apiuser.user_id in [reviewer.user_id for reviewer in pull_request.reviewers]
2539 if close_pr and not (apiuser.admin or owner):
2560 if close_pr and not (apiuser.admin or owner):
2540 raise JSONRPCError('No permission to close pull request. User needs to be admin or owner.')
2561 raise JSONRPCError('No permission to close pull request. User needs to be admin or owner.')
2541 if status and not (apiuser.admin or owner or reviewer):
2562 if status and not (apiuser.admin or owner or reviewer):
2542 raise JSONRPCError('No permission to change pull request status. User needs to be admin, owner or reviewer.')
2563 raise JSONRPCError('No permission to change pull request status. User needs to be admin, owner or reviewer.')
2543 if pull_request.is_closed():
2564 if pull_request.is_closed():
2544 raise JSONRPCError('pull request is already closed')
2565 raise JSONRPCError('pull request is already closed')
2545
2566
2546 comment = ChangesetCommentsModel().create(
2567 comment = ChangesetCommentsModel().create(
2547 text=comment_msg,
2568 text=comment_msg,
2548 repo=pull_request.org_repo.repo_id,
2569 repo=pull_request.org_repo.repo_id,
2549 author=apiuser.user_id,
2570 author=apiuser.user_id,
2550 pull_request=pull_request.pull_request_id,
2571 pull_request=pull_request.pull_request_id,
2551 f_path=None,
2572 f_path=None,
2552 line_no=None,
2573 line_no=None,
2553 status_change=(ChangesetStatus.get_status_lbl(status)),
2574 status_change=(ChangesetStatus.get_status_lbl(status)),
2554 closing_pr=close_pr
2575 closing_pr=close_pr
2555 )
2576 )
2556 action_logger(apiuser,
2577 action_logger(apiuser,
2557 'user_commented_pull_request:%s' % pull_request_id,
2578 'user_commented_pull_request:%s' % pull_request_id,
2558 pull_request.org_repo, request.ip_addr)
2579 pull_request.org_repo, request.ip_addr)
2559 if status:
2580 if status:
2560 ChangesetStatusModel().set_status(
2581 ChangesetStatusModel().set_status(
2561 pull_request.org_repo_id,
2582 pull_request.org_repo_id,
2562 status,
2583 status,
2563 apiuser.user_id,
2584 apiuser.user_id,
2564 comment,
2585 comment,
2565 pull_request=pull_request_id
2586 pull_request=pull_request_id
2566 )
2587 )
2567 if close_pr:
2588 if close_pr:
2568 PullRequestModel().close_pull_request(pull_request_id)
2589 PullRequestModel().close_pull_request(pull_request_id)
2569 action_logger(apiuser,
2590 action_logger(apiuser,
2570 'user_closed_pull_request:%s' % pull_request_id,
2591 'user_closed_pull_request:%s' % pull_request_id,
2571 pull_request.org_repo, request.ip_addr)
2592 pull_request.org_repo, request.ip_addr)
2572 Session().commit()
2593 Session().commit()
2573 return True
2594 return True
@@ -1,1062 +1,1075 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.base
3 vcs.backends.base
4 ~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~
5
5
6 Base for all available scm backends
6 Base for all available scm backends
7
7
8 :created_on: Apr 8, 2010
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
10 """
11
11
12 import datetime
12 import datetime
13 import itertools
13 import itertools
14
14
15 from kallithea.lib.vcs.utils import author_name, author_email, safe_unicode
15 from kallithea.lib.vcs.utils import author_name, author_email, safe_unicode
16 from kallithea.lib.vcs.utils.lazy import LazyProperty
16 from kallithea.lib.vcs.utils.lazy import LazyProperty
17 from kallithea.lib.vcs.utils.helpers import get_dict_for_attrs
17 from kallithea.lib.vcs.utils.helpers import get_dict_for_attrs
18 from kallithea.lib.vcs.conf import settings
18 from kallithea.lib.vcs.conf import settings
19
19
20 from kallithea.lib.vcs.exceptions import (
20 from kallithea.lib.vcs.exceptions import (
21 ChangesetError, EmptyRepositoryError, NodeAlreadyAddedError,
21 ChangesetError, EmptyRepositoryError, NodeAlreadyAddedError,
22 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
22 NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError,
23 NodeDoesNotExistError, NodeNotChangedError, RepositoryError
23 NodeDoesNotExistError, NodeNotChangedError, RepositoryError
24 )
24 )
25
25
26
26
27 class BaseRepository(object):
27 class BaseRepository(object):
28 """
28 """
29 Base Repository for final backends
29 Base Repository for final backends
30
30
31 **Attributes**
31 **Attributes**
32
32
33 ``DEFAULT_BRANCH_NAME``
33 ``DEFAULT_BRANCH_NAME``
34 name of default branch (i.e. "trunk" for svn, "master" for git etc.
34 name of default branch (i.e. "trunk" for svn, "master" for git etc.
35
35
36 ``scm``
36 ``scm``
37 alias of scm, i.e. *git* or *hg*
37 alias of scm, i.e. *git* or *hg*
38
38
39 ``repo``
39 ``repo``
40 object from external api
40 object from external api
41
41
42 ``revisions``
42 ``revisions``
43 list of all available revisions' ids, in ascending order
43 list of all available revisions' ids, in ascending order
44
44
45 ``changesets``
45 ``changesets``
46 storage dict caching returned changesets
46 storage dict caching returned changesets
47
47
48 ``path``
48 ``path``
49 absolute path to the repository
49 absolute path to the repository
50
50
51 ``branches``
51 ``branches``
52 branches as list of changesets
52 branches as list of changesets
53
53
54 ``tags``
54 ``tags``
55 tags as list of changesets
55 tags as list of changesets
56 """
56 """
57 scm = None
57 scm = None
58 DEFAULT_BRANCH_NAME = None
58 DEFAULT_BRANCH_NAME = None
59 EMPTY_CHANGESET = '0' * 40
59 EMPTY_CHANGESET = '0' * 40
60
60
61 def __init__(self, repo_path, create=False, **kwargs):
61 def __init__(self, repo_path, create=False, **kwargs):
62 """
62 """
63 Initializes repository. Raises RepositoryError if repository could
63 Initializes repository. Raises RepositoryError if repository could
64 not be find at the given ``repo_path`` or directory at ``repo_path``
64 not be find at the given ``repo_path`` or directory at ``repo_path``
65 exists and ``create`` is set to True.
65 exists and ``create`` is set to True.
66
66
67 :param repo_path: local path of the repository
67 :param repo_path: local path of the repository
68 :param create=False: if set to True, would try to create repository.
68 :param create=False: if set to True, would try to create repository.
69 :param src_url=None: if set, should be proper url from which repository
69 :param src_url=None: if set, should be proper url from which repository
70 would be cloned; requires ``create`` parameter to be set to True -
70 would be cloned; requires ``create`` parameter to be set to True -
71 raises RepositoryError if src_url is set and create evaluates to
71 raises RepositoryError if src_url is set and create evaluates to
72 False
72 False
73 """
73 """
74 raise NotImplementedError
74 raise NotImplementedError
75
75
76 def __str__(self):
76 def __str__(self):
77 return '<%s at %s>' % (self.__class__.__name__, self.path)
77 return '<%s at %s>' % (self.__class__.__name__, self.path)
78
78
79 def __repr__(self):
79 def __repr__(self):
80 return self.__str__()
80 return self.__str__()
81
81
82 def __len__(self):
82 def __len__(self):
83 return self.count()
83 return self.count()
84
84
85 def __eq__(self, other):
85 def __eq__(self, other):
86 same_instance = isinstance(other, self.__class__)
86 same_instance = isinstance(other, self.__class__)
87 return same_instance and getattr(other, 'path', None) == self.path
87 return same_instance and getattr(other, 'path', None) == self.path
88
88
89 def __ne__(self, other):
89 def __ne__(self, other):
90 return not self.__eq__(other)
90 return not self.__eq__(other)
91
91
92 @LazyProperty
92 @LazyProperty
93 def alias(self):
93 def alias(self):
94 for k, v in settings.BACKENDS.items():
94 for k, v in settings.BACKENDS.items():
95 if v.split('.')[-1] == str(self.__class__.__name__):
95 if v.split('.')[-1] == str(self.__class__.__name__):
96 return k
96 return k
97
97
98 @LazyProperty
98 @LazyProperty
99 def name(self):
99 def name(self):
100 """
100 """
101 Return repository name (without group name)
101 Return repository name (without group name)
102 """
102 """
103 raise NotImplementedError
103 raise NotImplementedError
104
104
105 @property
105 @property
106 def name_unicode(self):
106 def name_unicode(self):
107 return safe_unicode(self.name)
107 return safe_unicode(self.name)
108
108
109 @LazyProperty
109 @LazyProperty
110 def owner(self):
110 def owner(self):
111 raise NotImplementedError
111 raise NotImplementedError
112
112
113 @LazyProperty
113 @LazyProperty
114 def description(self):
114 def description(self):
115 raise NotImplementedError
115 raise NotImplementedError
116
116
117 @LazyProperty
117 @LazyProperty
118 def size(self):
118 def size(self):
119 """
119 """
120 Returns combined size in bytes for all repository files
120 Returns combined size in bytes for all repository files
121 """
121 """
122
122
123 size = 0
123 size = 0
124 try:
124 try:
125 tip = self.get_changeset()
125 tip = self.get_changeset()
126 for topnode, dirs, files in tip.walk('/'):
126 for topnode, dirs, files in tip.walk('/'):
127 for f in files:
127 for f in files:
128 size += tip.get_file_size(f.path)
128 size += tip.get_file_size(f.path)
129
129
130 except RepositoryError as e:
130 except RepositoryError as e:
131 pass
131 pass
132 return size
132 return size
133
133
134 def is_valid(self):
134 def is_valid(self):
135 """
135 """
136 Validates repository.
136 Validates repository.
137 """
137 """
138 raise NotImplementedError
138 raise NotImplementedError
139
139
140 def is_empty(self):
140 def is_empty(self):
141 return self._empty
141 return self._empty
142
142
143 #==========================================================================
143 #==========================================================================
144 # CHANGESETS
144 # CHANGESETS
145 #==========================================================================
145 #==========================================================================
146
146
147 def get_changeset(self, revision=None):
147 def get_changeset(self, revision=None):
148 """
148 """
149 Returns instance of ``Changeset`` class. If ``revision`` is None, most
149 Returns instance of ``Changeset`` class. If ``revision`` is None, most
150 recent changeset is returned.
150 recent changeset is returned.
151
151
152 :raises ``EmptyRepositoryError``: if there are no revisions
152 :raises ``EmptyRepositoryError``: if there are no revisions
153 """
153 """
154 raise NotImplementedError
154 raise NotImplementedError
155
155
156 def __iter__(self):
156 def __iter__(self):
157 """
157 """
158 Allows Repository objects to be iterated.
158 Allows Repository objects to be iterated.
159
159
160 *Requires* implementation of ``__getitem__`` method.
160 *Requires* implementation of ``__getitem__`` method.
161 """
161 """
162 for revision in self.revisions:
162 for revision in self.revisions:
163 yield self.get_changeset(revision)
163 yield self.get_changeset(revision)
164
164
165 def get_changesets(self, start=None, end=None, start_date=None,
165 def get_changesets(self, start=None, end=None, start_date=None,
166 end_date=None, branch_name=None, reverse=False):
166 end_date=None, branch_name=None, reverse=False):
167 """
167 """
168 Returns iterator of ``BaseChangeset`` objects from start to end,
168 Returns iterator of ``BaseChangeset`` objects from start to end,
169 both inclusive.
169 both inclusive.
170
170
171 :param start: None or str
171 :param start: None or str
172 :param end: None or str
172 :param end: None or str
173 :param start_date:
173 :param start_date:
174 :param end_date:
174 :param end_date:
175 :param branch_name:
175 :param branch_name:
176 :param reversed:
176 :param reversed:
177 """
177 """
178 raise NotImplementedError
178 raise NotImplementedError
179
179
180 def __getslice__(self, i, j):
180 def __getslice__(self, i, j):
181 """
181 """
182 Returns a iterator of sliced repository
182 Returns a iterator of sliced repository
183 """
183 """
184 for rev in self.revisions[i:j]:
184 for rev in self.revisions[i:j]:
185 yield self.get_changeset(rev)
185 yield self.get_changeset(rev)
186
186
187 def __getitem__(self, key):
187 def __getitem__(self, key):
188 return self.get_changeset(key)
188 return self.get_changeset(key)
189
189
190 def count(self):
190 def count(self):
191 return len(self.revisions)
191 return len(self.revisions)
192
192
193 def tag(self, name, user, revision=None, message=None, date=None, **opts):
193 def tag(self, name, user, revision=None, message=None, date=None, **opts):
194 """
194 """
195 Creates and returns a tag for the given ``revision``.
195 Creates and returns a tag for the given ``revision``.
196
196
197 :param name: name for new tag
197 :param name: name for new tag
198 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
198 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
199 :param revision: changeset id for which new tag would be created
199 :param revision: changeset id for which new tag would be created
200 :param message: message of the tag's commit
200 :param message: message of the tag's commit
201 :param date: date of tag's commit
201 :param date: date of tag's commit
202
202
203 :raises TagAlreadyExistError: if tag with same name already exists
203 :raises TagAlreadyExistError: if tag with same name already exists
204 """
204 """
205 raise NotImplementedError
205 raise NotImplementedError
206
206
207 def remove_tag(self, name, user, message=None, date=None):
207 def remove_tag(self, name, user, message=None, date=None):
208 """
208 """
209 Removes tag with the given ``name``.
209 Removes tag with the given ``name``.
210
210
211 :param name: name of the tag to be removed
211 :param name: name of the tag to be removed
212 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
212 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
213 :param message: message of the tag's removal commit
213 :param message: message of the tag's removal commit
214 :param date: date of tag's removal commit
214 :param date: date of tag's removal commit
215
215
216 :raises TagDoesNotExistError: if tag with given name does not exists
216 :raises TagDoesNotExistError: if tag with given name does not exists
217 """
217 """
218 raise NotImplementedError
218 raise NotImplementedError
219
219
220 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
220 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
221 context=3):
221 context=3):
222 """
222 """
223 Returns (git like) *diff*, as plain text. Shows changes introduced by
223 Returns (git like) *diff*, as plain text. Shows changes introduced by
224 ``rev2`` since ``rev1``.
224 ``rev2`` since ``rev1``.
225
225
226 :param rev1: Entry point from which diff is shown. Can be
226 :param rev1: Entry point from which diff is shown. Can be
227 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
227 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
228 the changes since empty state of the repository until ``rev2``
228 the changes since empty state of the repository until ``rev2``
229 :param rev2: Until which revision changes should be shown.
229 :param rev2: Until which revision changes should be shown.
230 :param ignore_whitespace: If set to ``True``, would not show whitespace
230 :param ignore_whitespace: If set to ``True``, would not show whitespace
231 changes. Defaults to ``False``.
231 changes. Defaults to ``False``.
232 :param context: How many lines before/after changed lines should be
232 :param context: How many lines before/after changed lines should be
233 shown. Defaults to ``3``.
233 shown. Defaults to ``3``.
234 """
234 """
235 raise NotImplementedError
235 raise NotImplementedError
236
236
237 # ========== #
237 # ========== #
238 # COMMIT API #
238 # COMMIT API #
239 # ========== #
239 # ========== #
240
240
241 @LazyProperty
241 @LazyProperty
242 def in_memory_changeset(self):
242 def in_memory_changeset(self):
243 """
243 """
244 Returns ``InMemoryChangeset`` object for this repository.
244 Returns ``InMemoryChangeset`` object for this repository.
245 """
245 """
246 raise NotImplementedError
246 raise NotImplementedError
247
247
248 def add(self, filenode, **kwargs):
248 def add(self, filenode, **kwargs):
249 """
249 """
250 Commit api function that will add given ``FileNode`` into this
250 Commit api function that will add given ``FileNode`` into this
251 repository.
251 repository.
252
252
253 :raises ``NodeAlreadyExistsError``: if there is a file with same path
253 :raises ``NodeAlreadyExistsError``: if there is a file with same path
254 already in repository
254 already in repository
255 :raises ``NodeAlreadyAddedError``: if given node is already marked as
255 :raises ``NodeAlreadyAddedError``: if given node is already marked as
256 *added*
256 *added*
257 """
257 """
258 raise NotImplementedError
258 raise NotImplementedError
259
259
260 def remove(self, filenode, **kwargs):
260 def remove(self, filenode, **kwargs):
261 """
261 """
262 Commit api function that will remove given ``FileNode`` into this
262 Commit api function that will remove given ``FileNode`` into this
263 repository.
263 repository.
264
264
265 :raises ``EmptyRepositoryError``: if there are no changesets yet
265 :raises ``EmptyRepositoryError``: if there are no changesets yet
266 :raises ``NodeDoesNotExistError``: if there is no file with given path
266 :raises ``NodeDoesNotExistError``: if there is no file with given path
267 """
267 """
268 raise NotImplementedError
268 raise NotImplementedError
269
269
270 def commit(self, message, **kwargs):
270 def commit(self, message, **kwargs):
271 """
271 """
272 Persists current changes made on this repository and returns newly
272 Persists current changes made on this repository and returns newly
273 created changeset.
273 created changeset.
274
274
275 :raises ``NothingChangedError``: if no changes has been made
275 :raises ``NothingChangedError``: if no changes has been made
276 """
276 """
277 raise NotImplementedError
277 raise NotImplementedError
278
278
279 def get_state(self):
279 def get_state(self):
280 """
280 """
281 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
281 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
282 containing ``FileNode`` objects.
282 containing ``FileNode`` objects.
283 """
283 """
284 raise NotImplementedError
284 raise NotImplementedError
285
285
286 def get_config_value(self, section, name, config_file=None):
286 def get_config_value(self, section, name, config_file=None):
287 """
287 """
288 Returns configuration value for a given [``section``] and ``name``.
288 Returns configuration value for a given [``section``] and ``name``.
289
289
290 :param section: Section we want to retrieve value from
290 :param section: Section we want to retrieve value from
291 :param name: Name of configuration we want to retrieve
291 :param name: Name of configuration we want to retrieve
292 :param config_file: A path to file which should be used to retrieve
292 :param config_file: A path to file which should be used to retrieve
293 configuration from (might also be a list of file paths)
293 configuration from (might also be a list of file paths)
294 """
294 """
295 raise NotImplementedError
295 raise NotImplementedError
296
296
297 def get_user_name(self, config_file=None):
297 def get_user_name(self, config_file=None):
298 """
298 """
299 Returns user's name from global configuration file.
299 Returns user's name from global configuration file.
300
300
301 :param config_file: A path to file which should be used to retrieve
301 :param config_file: A path to file which should be used to retrieve
302 configuration from (might also be a list of file paths)
302 configuration from (might also be a list of file paths)
303 """
303 """
304 raise NotImplementedError
304 raise NotImplementedError
305
305
306 def get_user_email(self, config_file=None):
306 def get_user_email(self, config_file=None):
307 """
307 """
308 Returns user's email from global configuration file.
308 Returns user's email from global configuration file.
309
309
310 :param config_file: A path to file which should be used to retrieve
310 :param config_file: A path to file which should be used to retrieve
311 configuration from (might also be a list of file paths)
311 configuration from (might also be a list of file paths)
312 """
312 """
313 raise NotImplementedError
313 raise NotImplementedError
314
314
315 # =========== #
315 # =========== #
316 # WORKDIR API #
316 # WORKDIR API #
317 # =========== #
317 # =========== #
318
318
319 @LazyProperty
319 @LazyProperty
320 def workdir(self):
320 def workdir(self):
321 """
321 """
322 Returns ``Workdir`` instance for this repository.
322 Returns ``Workdir`` instance for this repository.
323 """
323 """
324 raise NotImplementedError
324 raise NotImplementedError
325
325
326
326
327 class BaseChangeset(object):
327 class BaseChangeset(object):
328 """
328 """
329 Each backend should implement it's changeset representation.
329 Each backend should implement it's changeset representation.
330
330
331 **Attributes**
331 **Attributes**
332
332
333 ``repository``
333 ``repository``
334 repository object within which changeset exists
334 repository object within which changeset exists
335
335
336 ``id``
336 ``id``
337 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
337 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
338
338
339 ``raw_id``
339 ``raw_id``
340 raw changeset representation (i.e. full 40 length sha for git
340 raw changeset representation (i.e. full 40 length sha for git
341 backend)
341 backend)
342
342
343 ``short_id``
343 ``short_id``
344 shortened (if apply) version of ``raw_id``; it would be simple
344 shortened (if apply) version of ``raw_id``; it would be simple
345 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
345 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
346 as ``raw_id`` for subversion
346 as ``raw_id`` for subversion
347
347
348 ``revision``
348 ``revision``
349 revision number as integer
349 revision number as integer
350
350
351 ``files``
351 ``files``
352 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
352 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
353
353
354 ``dirs``
354 ``dirs``
355 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
355 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
356
356
357 ``nodes``
357 ``nodes``
358 combined list of ``Node`` objects
358 combined list of ``Node`` objects
359
359
360 ``author``
360 ``author``
361 author of the changeset, as unicode
361 author of the changeset, as unicode
362
362
363 ``message``
363 ``message``
364 message of the changeset, as unicode
364 message of the changeset, as unicode
365
365
366 ``parents``
366 ``parents``
367 list of parent changesets
367 list of parent changesets
368
368
369 ``last``
369 ``last``
370 ``True`` if this is last changeset in repository, ``False``
370 ``True`` if this is last changeset in repository, ``False``
371 otherwise; trying to access this attribute while there is no
371 otherwise; trying to access this attribute while there is no
372 changesets would raise ``EmptyRepositoryError``
372 changesets would raise ``EmptyRepositoryError``
373 """
373 """
374 def __str__(self):
374 def __str__(self):
375 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
375 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
376 self.short_id)
376 self.short_id)
377
377
378 def __repr__(self):
378 def __repr__(self):
379 return self.__str__()
379 return self.__str__()
380
380
381 def __unicode__(self):
381 def __unicode__(self):
382 return u'%s:%s' % (self.revision, self.short_id)
382 return u'%s:%s' % (self.revision, self.short_id)
383
383
384 def __eq__(self, other):
384 def __eq__(self, other):
385 return self.raw_id == other.raw_id
385 return self.raw_id == other.raw_id
386
386
387 def __json__(self):
387 def __json__(self, with_file_list=False):
388 return dict(
388 if with_file_list:
389 short_id=self.short_id,
389 return dict(
390 raw_id=self.raw_id,
390 short_id=self.short_id,
391 revision=self.revision,
391 raw_id=self.raw_id,
392 message=self.message,
392 revision=self.revision,
393 date=self.date,
393 message=self.message,
394 author=self.author,
394 date=self.date,
395 )
395 author=self.author,
396 added=[el.path for el in self.added],
397 changed=[el.path for el in self.changed],
398 removed=[el.path for el in self.removed],
399 )
400 else:
401 return dict(
402 short_id=self.short_id,
403 raw_id=self.raw_id,
404 revision=self.revision,
405 message=self.message,
406 date=self.date,
407 author=self.author,
408 )
396
409
397 @LazyProperty
410 @LazyProperty
398 def last(self):
411 def last(self):
399 if self.repository is None:
412 if self.repository is None:
400 raise ChangesetError("Cannot check if it's most recent revision")
413 raise ChangesetError("Cannot check if it's most recent revision")
401 return self.raw_id == self.repository.revisions[-1]
414 return self.raw_id == self.repository.revisions[-1]
402
415
403 @LazyProperty
416 @LazyProperty
404 def parents(self):
417 def parents(self):
405 """
418 """
406 Returns list of parents changesets.
419 Returns list of parents changesets.
407 """
420 """
408 raise NotImplementedError
421 raise NotImplementedError
409
422
410 @LazyProperty
423 @LazyProperty
411 def children(self):
424 def children(self):
412 """
425 """
413 Returns list of children changesets.
426 Returns list of children changesets.
414 """
427 """
415 raise NotImplementedError
428 raise NotImplementedError
416
429
417 @LazyProperty
430 @LazyProperty
418 def id(self):
431 def id(self):
419 """
432 """
420 Returns string identifying this changeset.
433 Returns string identifying this changeset.
421 """
434 """
422 raise NotImplementedError
435 raise NotImplementedError
423
436
424 @LazyProperty
437 @LazyProperty
425 def raw_id(self):
438 def raw_id(self):
426 """
439 """
427 Returns raw string identifying this changeset.
440 Returns raw string identifying this changeset.
428 """
441 """
429 raise NotImplementedError
442 raise NotImplementedError
430
443
431 @LazyProperty
444 @LazyProperty
432 def short_id(self):
445 def short_id(self):
433 """
446 """
434 Returns shortened version of ``raw_id`` attribute, as string,
447 Returns shortened version of ``raw_id`` attribute, as string,
435 identifying this changeset, useful for web representation.
448 identifying this changeset, useful for web representation.
436 """
449 """
437 raise NotImplementedError
450 raise NotImplementedError
438
451
439 @LazyProperty
452 @LazyProperty
440 def revision(self):
453 def revision(self):
441 """
454 """
442 Returns integer identifying this changeset.
455 Returns integer identifying this changeset.
443
456
444 """
457 """
445 raise NotImplementedError
458 raise NotImplementedError
446
459
447 @LazyProperty
460 @LazyProperty
448 def committer(self):
461 def committer(self):
449 """
462 """
450 Returns Committer for given commit
463 Returns Committer for given commit
451 """
464 """
452
465
453 raise NotImplementedError
466 raise NotImplementedError
454
467
455 @LazyProperty
468 @LazyProperty
456 def committer_name(self):
469 def committer_name(self):
457 """
470 """
458 Returns Author name for given commit
471 Returns Author name for given commit
459 """
472 """
460
473
461 return author_name(self.committer)
474 return author_name(self.committer)
462
475
463 @LazyProperty
476 @LazyProperty
464 def committer_email(self):
477 def committer_email(self):
465 """
478 """
466 Returns Author email address for given commit
479 Returns Author email address for given commit
467 """
480 """
468
481
469 return author_email(self.committer)
482 return author_email(self.committer)
470
483
471 @LazyProperty
484 @LazyProperty
472 def author(self):
485 def author(self):
473 """
486 """
474 Returns Author for given commit
487 Returns Author for given commit
475 """
488 """
476
489
477 raise NotImplementedError
490 raise NotImplementedError
478
491
479 @LazyProperty
492 @LazyProperty
480 def author_name(self):
493 def author_name(self):
481 """
494 """
482 Returns Author name for given commit
495 Returns Author name for given commit
483 """
496 """
484
497
485 return author_name(self.author)
498 return author_name(self.author)
486
499
487 @LazyProperty
500 @LazyProperty
488 def author_email(self):
501 def author_email(self):
489 """
502 """
490 Returns Author email address for given commit
503 Returns Author email address for given commit
491 """
504 """
492
505
493 return author_email(self.author)
506 return author_email(self.author)
494
507
495 def get_file_mode(self, path):
508 def get_file_mode(self, path):
496 """
509 """
497 Returns stat mode of the file at the given ``path``.
510 Returns stat mode of the file at the given ``path``.
498 """
511 """
499 raise NotImplementedError
512 raise NotImplementedError
500
513
501 def get_file_content(self, path):
514 def get_file_content(self, path):
502 """
515 """
503 Returns content of the file at the given ``path``.
516 Returns content of the file at the given ``path``.
504 """
517 """
505 raise NotImplementedError
518 raise NotImplementedError
506
519
507 def get_file_size(self, path):
520 def get_file_size(self, path):
508 """
521 """
509 Returns size of the file at the given ``path``.
522 Returns size of the file at the given ``path``.
510 """
523 """
511 raise NotImplementedError
524 raise NotImplementedError
512
525
513 def get_file_changeset(self, path):
526 def get_file_changeset(self, path):
514 """
527 """
515 Returns last commit of the file at the given ``path``.
528 Returns last commit of the file at the given ``path``.
516 """
529 """
517 raise NotImplementedError
530 raise NotImplementedError
518
531
519 def get_file_history(self, path):
532 def get_file_history(self, path):
520 """
533 """
521 Returns history of file as reversed list of ``Changeset`` objects for
534 Returns history of file as reversed list of ``Changeset`` objects for
522 which file at given ``path`` has been modified.
535 which file at given ``path`` has been modified.
523 """
536 """
524 raise NotImplementedError
537 raise NotImplementedError
525
538
526 def get_nodes(self, path):
539 def get_nodes(self, path):
527 """
540 """
528 Returns combined ``DirNode`` and ``FileNode`` objects list representing
541 Returns combined ``DirNode`` and ``FileNode`` objects list representing
529 state of changeset at the given ``path``.
542 state of changeset at the given ``path``.
530
543
531 :raises ``ChangesetError``: if node at the given ``path`` is not
544 :raises ``ChangesetError``: if node at the given ``path`` is not
532 instance of ``DirNode``
545 instance of ``DirNode``
533 """
546 """
534 raise NotImplementedError
547 raise NotImplementedError
535
548
536 def get_node(self, path):
549 def get_node(self, path):
537 """
550 """
538 Returns ``Node`` object from the given ``path``.
551 Returns ``Node`` object from the given ``path``.
539
552
540 :raises ``NodeDoesNotExistError``: if there is no node at the given
553 :raises ``NodeDoesNotExistError``: if there is no node at the given
541 ``path``
554 ``path``
542 """
555 """
543 raise NotImplementedError
556 raise NotImplementedError
544
557
545 def fill_archive(self, stream=None, kind='tgz', prefix=None):
558 def fill_archive(self, stream=None, kind='tgz', prefix=None):
546 """
559 """
547 Fills up given stream.
560 Fills up given stream.
548
561
549 :param stream: file like object.
562 :param stream: file like object.
550 :param kind: one of following: ``zip``, ``tar``, ``tgz``
563 :param kind: one of following: ``zip``, ``tar``, ``tgz``
551 or ``tbz2``. Default: ``tgz``.
564 or ``tbz2``. Default: ``tgz``.
552 :param prefix: name of root directory in archive.
565 :param prefix: name of root directory in archive.
553 Default is repository name and changeset's raw_id joined with dash.
566 Default is repository name and changeset's raw_id joined with dash.
554
567
555 repo-tip.<kind>
568 repo-tip.<kind>
556 """
569 """
557
570
558 raise NotImplementedError
571 raise NotImplementedError
559
572
560 def get_chunked_archive(self, **kwargs):
573 def get_chunked_archive(self, **kwargs):
561 """
574 """
562 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
575 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
563
576
564 :param chunk_size: extra parameter which controls size of returned
577 :param chunk_size: extra parameter which controls size of returned
565 chunks. Default:8k.
578 chunks. Default:8k.
566 """
579 """
567
580
568 chunk_size = kwargs.pop('chunk_size', 8192)
581 chunk_size = kwargs.pop('chunk_size', 8192)
569 stream = kwargs.get('stream')
582 stream = kwargs.get('stream')
570 self.fill_archive(**kwargs)
583 self.fill_archive(**kwargs)
571 while True:
584 while True:
572 data = stream.read(chunk_size)
585 data = stream.read(chunk_size)
573 if not data:
586 if not data:
574 break
587 break
575 yield data
588 yield data
576
589
577 @LazyProperty
590 @LazyProperty
578 def root(self):
591 def root(self):
579 """
592 """
580 Returns ``RootNode`` object for this changeset.
593 Returns ``RootNode`` object for this changeset.
581 """
594 """
582 return self.get_node('')
595 return self.get_node('')
583
596
584 def next(self, branch=None):
597 def next(self, branch=None):
585 """
598 """
586 Returns next changeset from current, if branch is gives it will return
599 Returns next changeset from current, if branch is gives it will return
587 next changeset belonging to this branch
600 next changeset belonging to this branch
588
601
589 :param branch: show changesets within the given named branch
602 :param branch: show changesets within the given named branch
590 """
603 """
591 raise NotImplementedError
604 raise NotImplementedError
592
605
593 def prev(self, branch=None):
606 def prev(self, branch=None):
594 """
607 """
595 Returns previous changeset from current, if branch is gives it will
608 Returns previous changeset from current, if branch is gives it will
596 return previous changeset belonging to this branch
609 return previous changeset belonging to this branch
597
610
598 :param branch: show changesets within the given named branch
611 :param branch: show changesets within the given named branch
599 """
612 """
600 raise NotImplementedError
613 raise NotImplementedError
601
614
602 @LazyProperty
615 @LazyProperty
603 def added(self):
616 def added(self):
604 """
617 """
605 Returns list of added ``FileNode`` objects.
618 Returns list of added ``FileNode`` objects.
606 """
619 """
607 raise NotImplementedError
620 raise NotImplementedError
608
621
609 @LazyProperty
622 @LazyProperty
610 def changed(self):
623 def changed(self):
611 """
624 """
612 Returns list of modified ``FileNode`` objects.
625 Returns list of modified ``FileNode`` objects.
613 """
626 """
614 raise NotImplementedError
627 raise NotImplementedError
615
628
616 @LazyProperty
629 @LazyProperty
617 def removed(self):
630 def removed(self):
618 """
631 """
619 Returns list of removed ``FileNode`` objects.
632 Returns list of removed ``FileNode`` objects.
620 """
633 """
621 raise NotImplementedError
634 raise NotImplementedError
622
635
623 @LazyProperty
636 @LazyProperty
624 def size(self):
637 def size(self):
625 """
638 """
626 Returns total number of bytes from contents of all filenodes.
639 Returns total number of bytes from contents of all filenodes.
627 """
640 """
628 return sum((node.size for node in self.get_filenodes_generator()))
641 return sum((node.size for node in self.get_filenodes_generator()))
629
642
630 def walk(self, topurl=''):
643 def walk(self, topurl=''):
631 """
644 """
632 Similar to os.walk method. Instead of filesystem it walks through
645 Similar to os.walk method. Instead of filesystem it walks through
633 changeset starting at given ``topurl``. Returns generator of tuples
646 changeset starting at given ``topurl``. Returns generator of tuples
634 (topnode, dirnodes, filenodes).
647 (topnode, dirnodes, filenodes).
635 """
648 """
636 topnode = self.get_node(topurl)
649 topnode = self.get_node(topurl)
637 yield (topnode, topnode.dirs, topnode.files)
650 yield (topnode, topnode.dirs, topnode.files)
638 for dirnode in topnode.dirs:
651 for dirnode in topnode.dirs:
639 for tup in self.walk(dirnode.path):
652 for tup in self.walk(dirnode.path):
640 yield tup
653 yield tup
641
654
642 def get_filenodes_generator(self):
655 def get_filenodes_generator(self):
643 """
656 """
644 Returns generator that yields *all* file nodes.
657 Returns generator that yields *all* file nodes.
645 """
658 """
646 for topnode, dirs, files in self.walk():
659 for topnode, dirs, files in self.walk():
647 for node in files:
660 for node in files:
648 yield node
661 yield node
649
662
650 def as_dict(self):
663 def as_dict(self):
651 """
664 """
652 Returns dictionary with changeset's attributes and their values.
665 Returns dictionary with changeset's attributes and their values.
653 """
666 """
654 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
667 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
655 'revision', 'date', 'message'])
668 'revision', 'date', 'message'])
656 data['author'] = {'name': self.author_name, 'email': self.author_email}
669 data['author'] = {'name': self.author_name, 'email': self.author_email}
657 data['added'] = [node.path for node in self.added]
670 data['added'] = [node.path for node in self.added]
658 data['changed'] = [node.path for node in self.changed]
671 data['changed'] = [node.path for node in self.changed]
659 data['removed'] = [node.path for node in self.removed]
672 data['removed'] = [node.path for node in self.removed]
660 return data
673 return data
661
674
662 @LazyProperty
675 @LazyProperty
663 def closesbranch(self):
676 def closesbranch(self):
664 return False
677 return False
665
678
666 @LazyProperty
679 @LazyProperty
667 def obsolete(self):
680 def obsolete(self):
668 return False
681 return False
669
682
670 @LazyProperty
683 @LazyProperty
671 def bumped(self):
684 def bumped(self):
672 return False
685 return False
673
686
674 @LazyProperty
687 @LazyProperty
675 def divergent(self):
688 def divergent(self):
676 return False
689 return False
677
690
678 @LazyProperty
691 @LazyProperty
679 def extinct(self):
692 def extinct(self):
680 return False
693 return False
681
694
682 @LazyProperty
695 @LazyProperty
683 def unstable(self):
696 def unstable(self):
684 return False
697 return False
685
698
686 @LazyProperty
699 @LazyProperty
687 def phase(self):
700 def phase(self):
688 return ''
701 return ''
689
702
690 class BaseWorkdir(object):
703 class BaseWorkdir(object):
691 """
704 """
692 Working directory representation of single repository.
705 Working directory representation of single repository.
693
706
694 :attribute: repository: repository object of working directory
707 :attribute: repository: repository object of working directory
695 """
708 """
696
709
697 def __init__(self, repository):
710 def __init__(self, repository):
698 self.repository = repository
711 self.repository = repository
699
712
700 def get_branch(self):
713 def get_branch(self):
701 """
714 """
702 Returns name of current branch.
715 Returns name of current branch.
703 """
716 """
704 raise NotImplementedError
717 raise NotImplementedError
705
718
706 def get_changeset(self):
719 def get_changeset(self):
707 """
720 """
708 Returns current changeset.
721 Returns current changeset.
709 """
722 """
710 raise NotImplementedError
723 raise NotImplementedError
711
724
712 def get_added(self):
725 def get_added(self):
713 """
726 """
714 Returns list of ``FileNode`` objects marked as *new* in working
727 Returns list of ``FileNode`` objects marked as *new* in working
715 directory.
728 directory.
716 """
729 """
717 raise NotImplementedError
730 raise NotImplementedError
718
731
719 def get_changed(self):
732 def get_changed(self):
720 """
733 """
721 Returns list of ``FileNode`` objects *changed* in working directory.
734 Returns list of ``FileNode`` objects *changed* in working directory.
722 """
735 """
723 raise NotImplementedError
736 raise NotImplementedError
724
737
725 def get_removed(self):
738 def get_removed(self):
726 """
739 """
727 Returns list of ``RemovedFileNode`` objects marked as *removed* in
740 Returns list of ``RemovedFileNode`` objects marked as *removed* in
728 working directory.
741 working directory.
729 """
742 """
730 raise NotImplementedError
743 raise NotImplementedError
731
744
732 def get_untracked(self):
745 def get_untracked(self):
733 """
746 """
734 Returns list of ``FileNode`` objects which are present within working
747 Returns list of ``FileNode`` objects which are present within working
735 directory however are not tracked by repository.
748 directory however are not tracked by repository.
736 """
749 """
737 raise NotImplementedError
750 raise NotImplementedError
738
751
739 def get_status(self):
752 def get_status(self):
740 """
753 """
741 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
754 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
742 lists.
755 lists.
743 """
756 """
744 raise NotImplementedError
757 raise NotImplementedError
745
758
746 def commit(self, message, **kwargs):
759 def commit(self, message, **kwargs):
747 """
760 """
748 Commits local (from working directory) changes and returns newly
761 Commits local (from working directory) changes and returns newly
749 created
762 created
750 ``Changeset``. Updates repository's ``revisions`` list.
763 ``Changeset``. Updates repository's ``revisions`` list.
751
764
752 :raises ``CommitError``: if any error occurs while committing
765 :raises ``CommitError``: if any error occurs while committing
753 """
766 """
754 raise NotImplementedError
767 raise NotImplementedError
755
768
756 def update(self, revision=None):
769 def update(self, revision=None):
757 """
770 """
758 Fetches content of the given revision and populates it within working
771 Fetches content of the given revision and populates it within working
759 directory.
772 directory.
760 """
773 """
761 raise NotImplementedError
774 raise NotImplementedError
762
775
763 def checkout_branch(self, branch=None):
776 def checkout_branch(self, branch=None):
764 """
777 """
765 Checks out ``branch`` or the backend's default branch.
778 Checks out ``branch`` or the backend's default branch.
766
779
767 Raises ``BranchDoesNotExistError`` if the branch does not exist.
780 Raises ``BranchDoesNotExistError`` if the branch does not exist.
768 """
781 """
769 raise NotImplementedError
782 raise NotImplementedError
770
783
771
784
772 class BaseInMemoryChangeset(object):
785 class BaseInMemoryChangeset(object):
773 """
786 """
774 Represents differences between repository's state (most recent head) and
787 Represents differences between repository's state (most recent head) and
775 changes made *in place*.
788 changes made *in place*.
776
789
777 **Attributes**
790 **Attributes**
778
791
779 ``repository``
792 ``repository``
780 repository object for this in-memory-changeset
793 repository object for this in-memory-changeset
781
794
782 ``added``
795 ``added``
783 list of ``FileNode`` objects marked as *added*
796 list of ``FileNode`` objects marked as *added*
784
797
785 ``changed``
798 ``changed``
786 list of ``FileNode`` objects marked as *changed*
799 list of ``FileNode`` objects marked as *changed*
787
800
788 ``removed``
801 ``removed``
789 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
802 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
790 *removed*
803 *removed*
791
804
792 ``parents``
805 ``parents``
793 list of ``Changeset`` representing parents of in-memory changeset.
806 list of ``Changeset`` representing parents of in-memory changeset.
794 Should always be 2-element sequence.
807 Should always be 2-element sequence.
795
808
796 """
809 """
797
810
798 def __init__(self, repository):
811 def __init__(self, repository):
799 self.repository = repository
812 self.repository = repository
800 self.added = []
813 self.added = []
801 self.changed = []
814 self.changed = []
802 self.removed = []
815 self.removed = []
803 self.parents = []
816 self.parents = []
804
817
805 def add(self, *filenodes):
818 def add(self, *filenodes):
806 """
819 """
807 Marks given ``FileNode`` objects as *to be committed*.
820 Marks given ``FileNode`` objects as *to be committed*.
808
821
809 :raises ``NodeAlreadyExistsError``: if node with same path exists at
822 :raises ``NodeAlreadyExistsError``: if node with same path exists at
810 latest changeset
823 latest changeset
811 :raises ``NodeAlreadyAddedError``: if node with same path is already
824 :raises ``NodeAlreadyAddedError``: if node with same path is already
812 marked as *added*
825 marked as *added*
813 """
826 """
814 # Check if not already marked as *added* first
827 # Check if not already marked as *added* first
815 for node in filenodes:
828 for node in filenodes:
816 if node.path in (n.path for n in self.added):
829 if node.path in (n.path for n in self.added):
817 raise NodeAlreadyAddedError("Such FileNode %s is already "
830 raise NodeAlreadyAddedError("Such FileNode %s is already "
818 "marked for addition" % node.path)
831 "marked for addition" % node.path)
819 for node in filenodes:
832 for node in filenodes:
820 self.added.append(node)
833 self.added.append(node)
821
834
822 def change(self, *filenodes):
835 def change(self, *filenodes):
823 """
836 """
824 Marks given ``FileNode`` objects to be *changed* in next commit.
837 Marks given ``FileNode`` objects to be *changed* in next commit.
825
838
826 :raises ``EmptyRepositoryError``: if there are no changesets yet
839 :raises ``EmptyRepositoryError``: if there are no changesets yet
827 :raises ``NodeAlreadyExistsError``: if node with same path is already
840 :raises ``NodeAlreadyExistsError``: if node with same path is already
828 marked to be *changed*
841 marked to be *changed*
829 :raises ``NodeAlreadyRemovedError``: if node with same path is already
842 :raises ``NodeAlreadyRemovedError``: if node with same path is already
830 marked to be *removed*
843 marked to be *removed*
831 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
844 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
832 changeset
845 changeset
833 :raises ``NodeNotChangedError``: if node hasn't really be changed
846 :raises ``NodeNotChangedError``: if node hasn't really be changed
834 """
847 """
835 for node in filenodes:
848 for node in filenodes:
836 if node.path in (n.path for n in self.removed):
849 if node.path in (n.path for n in self.removed):
837 raise NodeAlreadyRemovedError("Node at %s is already marked "
850 raise NodeAlreadyRemovedError("Node at %s is already marked "
838 "as removed" % node.path)
851 "as removed" % node.path)
839 try:
852 try:
840 self.repository.get_changeset()
853 self.repository.get_changeset()
841 except EmptyRepositoryError:
854 except EmptyRepositoryError:
842 raise EmptyRepositoryError("Nothing to change - try to *add* new "
855 raise EmptyRepositoryError("Nothing to change - try to *add* new "
843 "nodes rather than changing them")
856 "nodes rather than changing them")
844 for node in filenodes:
857 for node in filenodes:
845 if node.path in (n.path for n in self.changed):
858 if node.path in (n.path for n in self.changed):
846 raise NodeAlreadyChangedError("Node at '%s' is already "
859 raise NodeAlreadyChangedError("Node at '%s' is already "
847 "marked as changed" % node.path)
860 "marked as changed" % node.path)
848 self.changed.append(node)
861 self.changed.append(node)
849
862
850 def remove(self, *filenodes):
863 def remove(self, *filenodes):
851 """
864 """
852 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
865 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
853 *removed* in next commit.
866 *removed* in next commit.
854
867
855 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
868 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
856 be *removed*
869 be *removed*
857 :raises ``NodeAlreadyChangedError``: if node has been already marked to
870 :raises ``NodeAlreadyChangedError``: if node has been already marked to
858 be *changed*
871 be *changed*
859 """
872 """
860 for node in filenodes:
873 for node in filenodes:
861 if node.path in (n.path for n in self.removed):
874 if node.path in (n.path for n in self.removed):
862 raise NodeAlreadyRemovedError("Node is already marked to "
875 raise NodeAlreadyRemovedError("Node is already marked to "
863 "for removal at %s" % node.path)
876 "for removal at %s" % node.path)
864 if node.path in (n.path for n in self.changed):
877 if node.path in (n.path for n in self.changed):
865 raise NodeAlreadyChangedError("Node is already marked to "
878 raise NodeAlreadyChangedError("Node is already marked to "
866 "be changed at %s" % node.path)
879 "be changed at %s" % node.path)
867 # We only mark node as *removed* - real removal is done by
880 # We only mark node as *removed* - real removal is done by
868 # commit method
881 # commit method
869 self.removed.append(node)
882 self.removed.append(node)
870
883
871 def reset(self):
884 def reset(self):
872 """
885 """
873 Resets this instance to initial state (cleans ``added``, ``changed``
886 Resets this instance to initial state (cleans ``added``, ``changed``
874 and ``removed`` lists).
887 and ``removed`` lists).
875 """
888 """
876 self.added = []
889 self.added = []
877 self.changed = []
890 self.changed = []
878 self.removed = []
891 self.removed = []
879 self.parents = []
892 self.parents = []
880
893
881 def get_ipaths(self):
894 def get_ipaths(self):
882 """
895 """
883 Returns generator of paths from nodes marked as added, changed or
896 Returns generator of paths from nodes marked as added, changed or
884 removed.
897 removed.
885 """
898 """
886 for node in itertools.chain(self.added, self.changed, self.removed):
899 for node in itertools.chain(self.added, self.changed, self.removed):
887 yield node.path
900 yield node.path
888
901
889 def get_paths(self):
902 def get_paths(self):
890 """
903 """
891 Returns list of paths from nodes marked as added, changed or removed.
904 Returns list of paths from nodes marked as added, changed or removed.
892 """
905 """
893 return list(self.get_ipaths())
906 return list(self.get_ipaths())
894
907
895 def check_integrity(self, parents=None):
908 def check_integrity(self, parents=None):
896 """
909 """
897 Checks in-memory changeset's integrity. Also, sets parents if not
910 Checks in-memory changeset's integrity. Also, sets parents if not
898 already set.
911 already set.
899
912
900 :raises CommitError: if any error occurs (i.e.
913 :raises CommitError: if any error occurs (i.e.
901 ``NodeDoesNotExistError``).
914 ``NodeDoesNotExistError``).
902 """
915 """
903 if not self.parents:
916 if not self.parents:
904 parents = parents or []
917 parents = parents or []
905 if len(parents) == 0:
918 if len(parents) == 0:
906 try:
919 try:
907 parents = [self.repository.get_changeset(), None]
920 parents = [self.repository.get_changeset(), None]
908 except EmptyRepositoryError:
921 except EmptyRepositoryError:
909 parents = [None, None]
922 parents = [None, None]
910 elif len(parents) == 1:
923 elif len(parents) == 1:
911 parents += [None]
924 parents += [None]
912 self.parents = parents
925 self.parents = parents
913
926
914 # Local parents, only if not None
927 # Local parents, only if not None
915 parents = [p for p in self.parents if p]
928 parents = [p for p in self.parents if p]
916
929
917 # Check nodes marked as added
930 # Check nodes marked as added
918 for p in parents:
931 for p in parents:
919 for node in self.added:
932 for node in self.added:
920 try:
933 try:
921 p.get_node(node.path)
934 p.get_node(node.path)
922 except NodeDoesNotExistError:
935 except NodeDoesNotExistError:
923 pass
936 pass
924 else:
937 else:
925 raise NodeAlreadyExistsError("Node at %s already exists "
938 raise NodeAlreadyExistsError("Node at %s already exists "
926 "at %s" % (node.path, p))
939 "at %s" % (node.path, p))
927
940
928 # Check nodes marked as changed
941 # Check nodes marked as changed
929 missing = set(self.changed)
942 missing = set(self.changed)
930 not_changed = set(self.changed)
943 not_changed = set(self.changed)
931 if self.changed and not parents:
944 if self.changed and not parents:
932 raise NodeDoesNotExistError(str(self.changed[0].path))
945 raise NodeDoesNotExistError(str(self.changed[0].path))
933 for p in parents:
946 for p in parents:
934 for node in self.changed:
947 for node in self.changed:
935 try:
948 try:
936 old = p.get_node(node.path)
949 old = p.get_node(node.path)
937 missing.remove(node)
950 missing.remove(node)
938 # if content actually changed, remove node from unchanged
951 # if content actually changed, remove node from unchanged
939 if old.content != node.content:
952 if old.content != node.content:
940 not_changed.remove(node)
953 not_changed.remove(node)
941 except NodeDoesNotExistError:
954 except NodeDoesNotExistError:
942 pass
955 pass
943 if self.changed and missing:
956 if self.changed and missing:
944 raise NodeDoesNotExistError("Node at %s is missing "
957 raise NodeDoesNotExistError("Node at %s is missing "
945 "(parents: %s)" % (node.path, parents))
958 "(parents: %s)" % (node.path, parents))
946
959
947 if self.changed and not_changed:
960 if self.changed and not_changed:
948 raise NodeNotChangedError("Node at %s wasn't actually changed "
961 raise NodeNotChangedError("Node at %s wasn't actually changed "
949 "since parents' changesets: %s" % (not_changed.pop().path,
962 "since parents' changesets: %s" % (not_changed.pop().path,
950 parents)
963 parents)
951 )
964 )
952
965
953 # Check nodes marked as removed
966 # Check nodes marked as removed
954 if self.removed and not parents:
967 if self.removed and not parents:
955 raise NodeDoesNotExistError("Cannot remove node at %s as there "
968 raise NodeDoesNotExistError("Cannot remove node at %s as there "
956 "were no parents specified" % self.removed[0].path)
969 "were no parents specified" % self.removed[0].path)
957 really_removed = set()
970 really_removed = set()
958 for p in parents:
971 for p in parents:
959 for node in self.removed:
972 for node in self.removed:
960 try:
973 try:
961 p.get_node(node.path)
974 p.get_node(node.path)
962 really_removed.add(node)
975 really_removed.add(node)
963 except ChangesetError:
976 except ChangesetError:
964 pass
977 pass
965 not_removed = set(self.removed) - really_removed
978 not_removed = set(self.removed) - really_removed
966 if not_removed:
979 if not_removed:
967 raise NodeDoesNotExistError("Cannot remove node at %s from "
980 raise NodeDoesNotExistError("Cannot remove node at %s from "
968 "following parents: %s" % (not_removed[0], parents))
981 "following parents: %s" % (not_removed[0], parents))
969
982
970 def commit(self, message, author, parents=None, branch=None, date=None,
983 def commit(self, message, author, parents=None, branch=None, date=None,
971 **kwargs):
984 **kwargs):
972 """
985 """
973 Performs in-memory commit (doesn't check workdir in any way) and
986 Performs in-memory commit (doesn't check workdir in any way) and
974 returns newly created ``Changeset``. Updates repository's
987 returns newly created ``Changeset``. Updates repository's
975 ``revisions``.
988 ``revisions``.
976
989
977 .. note::
990 .. note::
978 While overriding this method each backend's should call
991 While overriding this method each backend's should call
979 ``self.check_integrity(parents)`` in the first place.
992 ``self.check_integrity(parents)`` in the first place.
980
993
981 :param message: message of the commit
994 :param message: message of the commit
982 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
995 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
983 :param parents: single parent or sequence of parents from which commit
996 :param parents: single parent or sequence of parents from which commit
984 would be derived
997 would be derived
985 :param date: ``datetime.datetime`` instance. Defaults to
998 :param date: ``datetime.datetime`` instance. Defaults to
986 ``datetime.datetime.now()``.
999 ``datetime.datetime.now()``.
987 :param branch: branch name, as string. If none given, default backend's
1000 :param branch: branch name, as string. If none given, default backend's
988 branch would be used.
1001 branch would be used.
989
1002
990 :raises ``CommitError``: if any error occurs while committing
1003 :raises ``CommitError``: if any error occurs while committing
991 """
1004 """
992 raise NotImplementedError
1005 raise NotImplementedError
993
1006
994
1007
995 class EmptyChangeset(BaseChangeset):
1008 class EmptyChangeset(BaseChangeset):
996 """
1009 """
997 An dummy empty changeset. It's possible to pass hash when creating
1010 An dummy empty changeset. It's possible to pass hash when creating
998 an EmptyChangeset
1011 an EmptyChangeset
999 """
1012 """
1000
1013
1001 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
1014 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
1002 alias=None, revision=-1, message='', author='', date=None):
1015 alias=None, revision=-1, message='', author='', date=None):
1003 self._empty_cs = cs
1016 self._empty_cs = cs
1004 self.revision = revision
1017 self.revision = revision
1005 self.message = message
1018 self.message = message
1006 self.author = author
1019 self.author = author
1007 self.date = date or datetime.datetime.fromtimestamp(0)
1020 self.date = date or datetime.datetime.fromtimestamp(0)
1008 self.repository = repo
1021 self.repository = repo
1009 self.requested_revision = requested_revision
1022 self.requested_revision = requested_revision
1010 self.alias = alias
1023 self.alias = alias
1011
1024
1012 @LazyProperty
1025 @LazyProperty
1013 def raw_id(self):
1026 def raw_id(self):
1014 """
1027 """
1015 Returns raw string identifying this changeset, useful for web
1028 Returns raw string identifying this changeset, useful for web
1016 representation.
1029 representation.
1017 """
1030 """
1018
1031
1019 return self._empty_cs
1032 return self._empty_cs
1020
1033
1021 @LazyProperty
1034 @LazyProperty
1022 def branch(self):
1035 def branch(self):
1023 from kallithea.lib.vcs.backends import get_backend
1036 from kallithea.lib.vcs.backends import get_backend
1024 return get_backend(self.alias).DEFAULT_BRANCH_NAME
1037 return get_backend(self.alias).DEFAULT_BRANCH_NAME
1025
1038
1026 @LazyProperty
1039 @LazyProperty
1027 def short_id(self):
1040 def short_id(self):
1028 return self.raw_id[:12]
1041 return self.raw_id[:12]
1029
1042
1030 def get_file_changeset(self, path):
1043 def get_file_changeset(self, path):
1031 return self
1044 return self
1032
1045
1033 def get_file_content(self, path):
1046 def get_file_content(self, path):
1034 return u''
1047 return u''
1035
1048
1036 def get_file_size(self, path):
1049 def get_file_size(self, path):
1037 return 0
1050 return 0
1038
1051
1039
1052
1040 class CollectionGenerator(object):
1053 class CollectionGenerator(object):
1041
1054
1042 def __init__(self, repo, revs):
1055 def __init__(self, repo, revs):
1043 self.repo = repo
1056 self.repo = repo
1044 self.revs = revs
1057 self.revs = revs
1045
1058
1046 def __len__(self):
1059 def __len__(self):
1047 return len(self.revs)
1060 return len(self.revs)
1048
1061
1049 def __iter__(self):
1062 def __iter__(self):
1050 for rev in self.revs:
1063 for rev in self.revs:
1051 yield self.repo.get_changeset(rev)
1064 yield self.repo.get_changeset(rev)
1052
1065
1053 def __getitem__(self, what):
1066 def __getitem__(self, what):
1054 """Return either a single element by index, or a sliced collection."""
1067 """Return either a single element by index, or a sliced collection."""
1055 if isinstance(what, slice):
1068 if isinstance(what, slice):
1056 return CollectionGenerator(self.repo, self.revs[what])
1069 return CollectionGenerator(self.repo, self.revs[what])
1057 else:
1070 else:
1058 # single item
1071 # single item
1059 return self.repo.get_changeset(self.revs[what])
1072 return self.repo.get_changeset(self.revs[what])
1060
1073
1061 def __repr__(self):
1074 def __repr__(self):
1062 return '<CollectionGenerator[len:%s]>' % (len(self))
1075 return '<CollectionGenerator[len:%s]>' % (len(self))
@@ -1,2596 +1,2614 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 """
15 """
16 Tests for the JSON-RPC web api.
16 Tests for the JSON-RPC web api.
17 """
17 """
18
18
19 import os
19 import os
20 import random
20 import random
21 import mock
21 import mock
22 import re
22 import re
23
23
24 from kallithea.tests.base import *
24 from kallithea.tests.base import *
25 from kallithea.tests.fixture import Fixture
25 from kallithea.tests.fixture import Fixture
26 from kallithea.lib.compat import json
26 from kallithea.lib.compat import json
27 from kallithea.lib.auth import AuthUser
27 from kallithea.lib.auth import AuthUser
28 from kallithea.model.user import UserModel
28 from kallithea.model.user import UserModel
29 from kallithea.model.user_group import UserGroupModel
29 from kallithea.model.user_group import UserGroupModel
30 from kallithea.model.repo import RepoModel
30 from kallithea.model.repo import RepoModel
31 from kallithea.model.repo_group import RepoGroupModel
31 from kallithea.model.repo_group import RepoGroupModel
32 from kallithea.model.meta import Session
32 from kallithea.model.meta import Session
33 from kallithea.model.scm import ScmModel
33 from kallithea.model.scm import ScmModel
34 from kallithea.model.gist import GistModel
34 from kallithea.model.gist import GistModel
35 from kallithea.model.changeset_status import ChangesetStatusModel
35 from kallithea.model.changeset_status import ChangesetStatusModel
36 from kallithea.model.db import Repository, User, Setting, Ui, PullRequest, ChangesetStatus
36 from kallithea.model.db import Repository, User, Setting, Ui, PullRequest, ChangesetStatus
37 from kallithea.lib.utils2 import time_to_datetime
37 from kallithea.lib.utils2 import time_to_datetime
38
38
39
39
40 API_URL = '/_admin/api'
40 API_URL = '/_admin/api'
41 TEST_USER_GROUP = u'test_user_group'
41 TEST_USER_GROUP = u'test_user_group'
42 TEST_REPO_GROUP = u'test_repo_group'
42 TEST_REPO_GROUP = u'test_repo_group'
43
43
44 fixture = Fixture()
44 fixture = Fixture()
45
45
46
46
47 def _build_data(apikey, method, **kw):
47 def _build_data(apikey, method, **kw):
48 """
48 """
49 Builds API data with given random ID
49 Builds API data with given random ID
50
50
51 :param random_id:
51 :param random_id:
52 """
52 """
53 random_id = random.randrange(1, 9999)
53 random_id = random.randrange(1, 9999)
54 return random_id, json.dumps({
54 return random_id, json.dumps({
55 "id": random_id,
55 "id": random_id,
56 "api_key": apikey,
56 "api_key": apikey,
57 "method": method,
57 "method": method,
58 "args": kw
58 "args": kw
59 })
59 })
60
60
61
61
62 jsonify = lambda obj: json.loads(json.dumps(obj))
62 jsonify = lambda obj: json.loads(json.dumps(obj))
63
63
64
64
65 def crash(*args, **kwargs):
65 def crash(*args, **kwargs):
66 raise Exception('Total Crash !')
66 raise Exception('Total Crash !')
67
67
68
68
69 def api_call(test_obj, params):
69 def api_call(test_obj, params):
70 response = test_obj.app.post(API_URL, content_type='application/json',
70 response = test_obj.app.post(API_URL, content_type='application/json',
71 params=params)
71 params=params)
72 return response
72 return response
73
73
74
74
75 ## helpers
75 ## helpers
76 def make_user_group(name=TEST_USER_GROUP):
76 def make_user_group(name=TEST_USER_GROUP):
77 gr = fixture.create_user_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
77 gr = fixture.create_user_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
78 UserGroupModel().add_user_to_group(user_group=gr,
78 UserGroupModel().add_user_to_group(user_group=gr,
79 user=TEST_USER_ADMIN_LOGIN)
79 user=TEST_USER_ADMIN_LOGIN)
80 Session().commit()
80 Session().commit()
81 return gr
81 return gr
82
82
83
83
84 def make_repo_group(name=TEST_REPO_GROUP):
84 def make_repo_group(name=TEST_REPO_GROUP):
85 gr = fixture.create_repo_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
85 gr = fixture.create_repo_group(name, cur_user=TEST_USER_ADMIN_LOGIN)
86 Session().commit()
86 Session().commit()
87 return gr
87 return gr
88
88
89
89
90 class _BaseTestApi(object):
90 class _BaseTestApi(object):
91 REPO = None
91 REPO = None
92 REPO_TYPE = None
92 REPO_TYPE = None
93
93
94 @classmethod
94 @classmethod
95 def setup_class(cls):
95 def setup_class(cls):
96 cls.usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
96 cls.usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
97 cls.apikey = cls.usr.api_key
97 cls.apikey = cls.usr.api_key
98 cls.test_user = UserModel().create_or_update(
98 cls.test_user = UserModel().create_or_update(
99 username='test-api',
99 username='test-api',
100 password='test',
100 password='test',
101 email='test@example.com',
101 email='test@example.com',
102 firstname=u'first',
102 firstname=u'first',
103 lastname=u'last'
103 lastname=u'last'
104 )
104 )
105 Session().commit()
105 Session().commit()
106 cls.TEST_USER_LOGIN = cls.test_user.username
106 cls.TEST_USER_LOGIN = cls.test_user.username
107 cls.apikey_regular = cls.test_user.api_key
107 cls.apikey_regular = cls.test_user.api_key
108
108
109 @classmethod
109 @classmethod
110 def teardown_class(cls):
110 def teardown_class(cls):
111 pass
111 pass
112
112
113 def setup_method(self, method):
113 def setup_method(self, method):
114 make_user_group()
114 make_user_group()
115 make_repo_group()
115 make_repo_group()
116
116
117 def teardown_method(self, method):
117 def teardown_method(self, method):
118 fixture.destroy_user_group(TEST_USER_GROUP)
118 fixture.destroy_user_group(TEST_USER_GROUP)
119 fixture.destroy_gists()
119 fixture.destroy_gists()
120 fixture.destroy_repo_group(TEST_REPO_GROUP)
120 fixture.destroy_repo_group(TEST_REPO_GROUP)
121
121
122 def _compare_ok(self, id_, expected, given):
122 def _compare_ok(self, id_, expected, given):
123 expected = jsonify({
123 expected = jsonify({
124 'id': id_,
124 'id': id_,
125 'error': None,
125 'error': None,
126 'result': expected
126 'result': expected
127 })
127 })
128 given = json.loads(given)
128 given = json.loads(given)
129 assert expected == given
129 assert expected == given
130
130
131 def _compare_error(self, id_, expected, given):
131 def _compare_error(self, id_, expected, given):
132 expected = jsonify({
132 expected = jsonify({
133 'id': id_,
133 'id': id_,
134 'error': expected,
134 'error': expected,
135 'result': None
135 'result': None
136 })
136 })
137 given = json.loads(given)
137 given = json.loads(given)
138 assert expected == given
138 assert expected == given
139
139
140 def test_Optional_object(self):
140 def test_Optional_object(self):
141 from kallithea.controllers.api.api import Optional
141 from kallithea.controllers.api.api import Optional
142
142
143 option1 = Optional(None)
143 option1 = Optional(None)
144 assert '<Optional:%s>' % None == repr(option1)
144 assert '<Optional:%s>' % None == repr(option1)
145 assert option1() == None
145 assert option1() == None
146
146
147 assert 1 == Optional.extract(Optional(1))
147 assert 1 == Optional.extract(Optional(1))
148 assert 'trololo' == Optional.extract('trololo')
148 assert 'trololo' == Optional.extract('trololo')
149
149
150 def test_Optional_OAttr(self):
150 def test_Optional_OAttr(self):
151 from kallithea.controllers.api.api import Optional, OAttr
151 from kallithea.controllers.api.api import Optional, OAttr
152
152
153 option1 = Optional(OAttr('apiuser'))
153 option1 = Optional(OAttr('apiuser'))
154 assert 'apiuser' == Optional.extract(option1)
154 assert 'apiuser' == Optional.extract(option1)
155
155
156 def test_OAttr_object(self):
156 def test_OAttr_object(self):
157 from kallithea.controllers.api.api import OAttr
157 from kallithea.controllers.api.api import OAttr
158
158
159 oattr1 = OAttr('apiuser')
159 oattr1 = OAttr('apiuser')
160 assert '<OptionalAttr:apiuser>' == repr(oattr1)
160 assert '<OptionalAttr:apiuser>' == repr(oattr1)
161 assert oattr1() == oattr1
161 assert oattr1() == oattr1
162
162
163 def test_api_wrong_key(self):
163 def test_api_wrong_key(self):
164 id_, params = _build_data('trololo', 'get_user')
164 id_, params = _build_data('trololo', 'get_user')
165 response = api_call(self, params)
165 response = api_call(self, params)
166
166
167 expected = 'Invalid API key'
167 expected = 'Invalid API key'
168 self._compare_error(id_, expected, given=response.body)
168 self._compare_error(id_, expected, given=response.body)
169
169
170 def test_api_missing_non_optional_param(self):
170 def test_api_missing_non_optional_param(self):
171 id_, params = _build_data(self.apikey, 'get_repo')
171 id_, params = _build_data(self.apikey, 'get_repo')
172 response = api_call(self, params)
172 response = api_call(self, params)
173
173
174 expected = 'Missing non optional `repoid` arg in JSON DATA'
174 expected = 'Missing non optional `repoid` arg in JSON DATA'
175 self._compare_error(id_, expected, given=response.body)
175 self._compare_error(id_, expected, given=response.body)
176
176
177 def test_api_missing_non_optional_param_args_null(self):
177 def test_api_missing_non_optional_param_args_null(self):
178 id_, params = _build_data(self.apikey, 'get_repo')
178 id_, params = _build_data(self.apikey, 'get_repo')
179 params = params.replace('"args": {}', '"args": null')
179 params = params.replace('"args": {}', '"args": null')
180 response = api_call(self, params)
180 response = api_call(self, params)
181
181
182 expected = 'Missing non optional `repoid` arg in JSON DATA'
182 expected = 'Missing non optional `repoid` arg in JSON DATA'
183 self._compare_error(id_, expected, given=response.body)
183 self._compare_error(id_, expected, given=response.body)
184
184
185 def test_api_missing_non_optional_param_args_bad(self):
185 def test_api_missing_non_optional_param_args_bad(self):
186 id_, params = _build_data(self.apikey, 'get_repo')
186 id_, params = _build_data(self.apikey, 'get_repo')
187 params = params.replace('"args": {}', '"args": 1')
187 params = params.replace('"args": {}', '"args": 1')
188 response = api_call(self, params)
188 response = api_call(self, params)
189
189
190 expected = 'Missing non optional `repoid` arg in JSON DATA'
190 expected = 'Missing non optional `repoid` arg in JSON DATA'
191 self._compare_error(id_, expected, given=response.body)
191 self._compare_error(id_, expected, given=response.body)
192
192
193 def test_api_args_is_null(self):
193 def test_api_args_is_null(self):
194 id_, params = _build_data(self.apikey, 'get_users', )
194 id_, params = _build_data(self.apikey, 'get_users', )
195 params = params.replace('"args": {}', '"args": null')
195 params = params.replace('"args": {}', '"args": null')
196 response = api_call(self, params)
196 response = api_call(self, params)
197 assert response.status == '200 OK'
197 assert response.status == '200 OK'
198
198
199 def test_api_args_is_bad(self):
199 def test_api_args_is_bad(self):
200 id_, params = _build_data(self.apikey, 'get_users', )
200 id_, params = _build_data(self.apikey, 'get_users', )
201 params = params.replace('"args": {}', '"args": 1')
201 params = params.replace('"args": {}', '"args": 1')
202 response = api_call(self, params)
202 response = api_call(self, params)
203 assert response.status == '200 OK'
203 assert response.status == '200 OK'
204
204
205 def test_api_args_different_args(self):
205 def test_api_args_different_args(self):
206 import string
206 import string
207 expected = {
207 expected = {
208 'ascii_letters': string.ascii_letters,
208 'ascii_letters': string.ascii_letters,
209 'ws': string.whitespace,
209 'ws': string.whitespace,
210 'printables': string.printable
210 'printables': string.printable
211 }
211 }
212 id_, params = _build_data(self.apikey, 'test', args=expected)
212 id_, params = _build_data(self.apikey, 'test', args=expected)
213 response = api_call(self, params)
213 response = api_call(self, params)
214 assert response.status == '200 OK'
214 assert response.status == '200 OK'
215 self._compare_ok(id_, expected, response.body)
215 self._compare_ok(id_, expected, response.body)
216
216
217 def test_api_get_users(self):
217 def test_api_get_users(self):
218 id_, params = _build_data(self.apikey, 'get_users', )
218 id_, params = _build_data(self.apikey, 'get_users', )
219 response = api_call(self, params)
219 response = api_call(self, params)
220 ret_all = []
220 ret_all = []
221 _users = User.query().filter_by(is_default_user=False) \
221 _users = User.query().filter_by(is_default_user=False) \
222 .order_by(User.username).all()
222 .order_by(User.username).all()
223 for usr in _users:
223 for usr in _users:
224 ret = usr.get_api_data()
224 ret = usr.get_api_data()
225 ret_all.append(jsonify(ret))
225 ret_all.append(jsonify(ret))
226 expected = ret_all
226 expected = ret_all
227 self._compare_ok(id_, expected, given=response.body)
227 self._compare_ok(id_, expected, given=response.body)
228
228
229 def test_api_get_user(self):
229 def test_api_get_user(self):
230 id_, params = _build_data(self.apikey, 'get_user',
230 id_, params = _build_data(self.apikey, 'get_user',
231 userid=TEST_USER_ADMIN_LOGIN)
231 userid=TEST_USER_ADMIN_LOGIN)
232 response = api_call(self, params)
232 response = api_call(self, params)
233
233
234 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
234 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
235 ret = usr.get_api_data()
235 ret = usr.get_api_data()
236 ret['permissions'] = AuthUser(dbuser=usr).permissions
236 ret['permissions'] = AuthUser(dbuser=usr).permissions
237
237
238 expected = ret
238 expected = ret
239 self._compare_ok(id_, expected, given=response.body)
239 self._compare_ok(id_, expected, given=response.body)
240
240
241 def test_api_get_user_that_does_not_exist(self):
241 def test_api_get_user_that_does_not_exist(self):
242 id_, params = _build_data(self.apikey, 'get_user',
242 id_, params = _build_data(self.apikey, 'get_user',
243 userid='trololo')
243 userid='trololo')
244 response = api_call(self, params)
244 response = api_call(self, params)
245
245
246 expected = "user `%s` does not exist" % 'trololo'
246 expected = "user `%s` does not exist" % 'trololo'
247 self._compare_error(id_, expected, given=response.body)
247 self._compare_error(id_, expected, given=response.body)
248
248
249 def test_api_get_user_without_giving_userid(self):
249 def test_api_get_user_without_giving_userid(self):
250 id_, params = _build_data(self.apikey, 'get_user')
250 id_, params = _build_data(self.apikey, 'get_user')
251 response = api_call(self, params)
251 response = api_call(self, params)
252
252
253 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
253 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
254 ret = usr.get_api_data()
254 ret = usr.get_api_data()
255 ret['permissions'] = AuthUser(dbuser=usr).permissions
255 ret['permissions'] = AuthUser(dbuser=usr).permissions
256
256
257 expected = ret
257 expected = ret
258 self._compare_ok(id_, expected, given=response.body)
258 self._compare_ok(id_, expected, given=response.body)
259
259
260 def test_api_get_user_without_giving_userid_non_admin(self):
260 def test_api_get_user_without_giving_userid_non_admin(self):
261 id_, params = _build_data(self.apikey_regular, 'get_user')
261 id_, params = _build_data(self.apikey_regular, 'get_user')
262 response = api_call(self, params)
262 response = api_call(self, params)
263
263
264 usr = User.get_by_username(self.TEST_USER_LOGIN)
264 usr = User.get_by_username(self.TEST_USER_LOGIN)
265 ret = usr.get_api_data()
265 ret = usr.get_api_data()
266 ret['permissions'] = AuthUser(dbuser=usr).permissions
266 ret['permissions'] = AuthUser(dbuser=usr).permissions
267
267
268 expected = ret
268 expected = ret
269 self._compare_ok(id_, expected, given=response.body)
269 self._compare_ok(id_, expected, given=response.body)
270
270
271 def test_api_get_user_with_giving_userid_non_admin(self):
271 def test_api_get_user_with_giving_userid_non_admin(self):
272 id_, params = _build_data(self.apikey_regular, 'get_user',
272 id_, params = _build_data(self.apikey_regular, 'get_user',
273 userid=self.TEST_USER_LOGIN)
273 userid=self.TEST_USER_LOGIN)
274 response = api_call(self, params)
274 response = api_call(self, params)
275
275
276 expected = 'userid is not the same as your user'
276 expected = 'userid is not the same as your user'
277 self._compare_error(id_, expected, given=response.body)
277 self._compare_error(id_, expected, given=response.body)
278
278
279 def test_api_pull(self):
279 def test_api_pull(self):
280 repo_name = u'test_pull'
280 repo_name = u'test_pull'
281 r = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
281 r = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
282 r.clone_uri = os.path.join(Ui.get_by_key('paths', '/').ui_value, self.REPO)
282 r.clone_uri = os.path.join(Ui.get_by_key('paths', '/').ui_value, self.REPO)
283 Session().commit()
283 Session().commit()
284
284
285 id_, params = _build_data(self.apikey, 'pull',
285 id_, params = _build_data(self.apikey, 'pull',
286 repoid=repo_name,)
286 repoid=repo_name,)
287 response = api_call(self, params)
287 response = api_call(self, params)
288
288
289 expected = {'msg': 'Pulled from `%s`' % repo_name,
289 expected = {'msg': 'Pulled from `%s`' % repo_name,
290 'repository': repo_name}
290 'repository': repo_name}
291 self._compare_ok(id_, expected, given=response.body)
291 self._compare_ok(id_, expected, given=response.body)
292
292
293 fixture.destroy_repo(repo_name)
293 fixture.destroy_repo(repo_name)
294
294
295 def test_api_pull_error(self):
295 def test_api_pull_error(self):
296 id_, params = _build_data(self.apikey, 'pull',
296 id_, params = _build_data(self.apikey, 'pull',
297 repoid=self.REPO, )
297 repoid=self.REPO, )
298 response = api_call(self, params)
298 response = api_call(self, params)
299
299
300 expected = 'Unable to pull changes from `%s`' % self.REPO
300 expected = 'Unable to pull changes from `%s`' % self.REPO
301 self._compare_error(id_, expected, given=response.body)
301 self._compare_error(id_, expected, given=response.body)
302
302
303 def test_api_rescan_repos(self):
303 def test_api_rescan_repos(self):
304 id_, params = _build_data(self.apikey, 'rescan_repos')
304 id_, params = _build_data(self.apikey, 'rescan_repos')
305 response = api_call(self, params)
305 response = api_call(self, params)
306
306
307 expected = {'added': [], 'removed': []}
307 expected = {'added': [], 'removed': []}
308 self._compare_ok(id_, expected, given=response.body)
308 self._compare_ok(id_, expected, given=response.body)
309
309
310 @mock.patch.object(ScmModel, 'repo_scan', crash)
310 @mock.patch.object(ScmModel, 'repo_scan', crash)
311 def test_api_rescann_error(self):
311 def test_api_rescann_error(self):
312 id_, params = _build_data(self.apikey, 'rescan_repos', )
312 id_, params = _build_data(self.apikey, 'rescan_repos', )
313 response = api_call(self, params)
313 response = api_call(self, params)
314
314
315 expected = 'Error occurred during rescan repositories action'
315 expected = 'Error occurred during rescan repositories action'
316 self._compare_error(id_, expected, given=response.body)
316 self._compare_error(id_, expected, given=response.body)
317
317
318 def test_api_invalidate_cache(self):
318 def test_api_invalidate_cache(self):
319 repo = RepoModel().get_by_repo_name(self.REPO)
319 repo = RepoModel().get_by_repo_name(self.REPO)
320 repo.scm_instance_cached() # seed cache
320 repo.scm_instance_cached() # seed cache
321
321
322 id_, params = _build_data(self.apikey, 'invalidate_cache',
322 id_, params = _build_data(self.apikey, 'invalidate_cache',
323 repoid=self.REPO)
323 repoid=self.REPO)
324 response = api_call(self, params)
324 response = api_call(self, params)
325
325
326 expected = {
326 expected = {
327 'msg': "Cache for repository `%s` was invalidated" % (self.REPO,),
327 'msg': "Cache for repository `%s` was invalidated" % (self.REPO,),
328 'repository': self.REPO
328 'repository': self.REPO
329 }
329 }
330 self._compare_ok(id_, expected, given=response.body)
330 self._compare_ok(id_, expected, given=response.body)
331
331
332 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
332 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
333 def test_api_invalidate_cache_error(self):
333 def test_api_invalidate_cache_error(self):
334 id_, params = _build_data(self.apikey, 'invalidate_cache',
334 id_, params = _build_data(self.apikey, 'invalidate_cache',
335 repoid=self.REPO)
335 repoid=self.REPO)
336 response = api_call(self, params)
336 response = api_call(self, params)
337
337
338 expected = 'Error occurred during cache invalidation action'
338 expected = 'Error occurred during cache invalidation action'
339 self._compare_error(id_, expected, given=response.body)
339 self._compare_error(id_, expected, given=response.body)
340
340
341 def test_api_invalidate_cache_regular_user_no_permission(self):
341 def test_api_invalidate_cache_regular_user_no_permission(self):
342 repo = RepoModel().get_by_repo_name(self.REPO)
342 repo = RepoModel().get_by_repo_name(self.REPO)
343 repo.scm_instance_cached() # seed cache
343 repo.scm_instance_cached() # seed cache
344
344
345 id_, params = _build_data(self.apikey_regular, 'invalidate_cache',
345 id_, params = _build_data(self.apikey_regular, 'invalidate_cache',
346 repoid=self.REPO)
346 repoid=self.REPO)
347 response = api_call(self, params)
347 response = api_call(self, params)
348
348
349 expected = "repository `%s` does not exist" % (self.REPO,)
349 expected = "repository `%s` does not exist" % (self.REPO,)
350 self._compare_error(id_, expected, given=response.body)
350 self._compare_error(id_, expected, given=response.body)
351
351
352 def test_api_lock_repo_lock_acquire(self):
352 def test_api_lock_repo_lock_acquire(self):
353 id_, params = _build_data(self.apikey, 'lock',
353 id_, params = _build_data(self.apikey, 'lock',
354 userid=TEST_USER_ADMIN_LOGIN,
354 userid=TEST_USER_ADMIN_LOGIN,
355 repoid=self.REPO,
355 repoid=self.REPO,
356 locked=True)
356 locked=True)
357 response = api_call(self, params)
357 response = api_call(self, params)
358 expected = {
358 expected = {
359 'repo': self.REPO, 'locked': True,
359 'repo': self.REPO, 'locked': True,
360 'locked_since': response.json['result']['locked_since'],
360 'locked_since': response.json['result']['locked_since'],
361 'locked_by': TEST_USER_ADMIN_LOGIN,
361 'locked_by': TEST_USER_ADMIN_LOGIN,
362 'lock_state_changed': True,
362 'lock_state_changed': True,
363 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
363 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
364 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
364 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
365 }
365 }
366 self._compare_ok(id_, expected, given=response.body)
366 self._compare_ok(id_, expected, given=response.body)
367
367
368 def test_api_lock_repo_lock_acquire_by_non_admin(self):
368 def test_api_lock_repo_lock_acquire_by_non_admin(self):
369 repo_name = u'api_delete_me'
369 repo_name = u'api_delete_me'
370 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
370 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
371 cur_user=self.TEST_USER_LOGIN)
371 cur_user=self.TEST_USER_LOGIN)
372 try:
372 try:
373 id_, params = _build_data(self.apikey_regular, 'lock',
373 id_, params = _build_data(self.apikey_regular, 'lock',
374 repoid=repo_name,
374 repoid=repo_name,
375 locked=True)
375 locked=True)
376 response = api_call(self, params)
376 response = api_call(self, params)
377 expected = {
377 expected = {
378 'repo': repo_name,
378 'repo': repo_name,
379 'locked': True,
379 'locked': True,
380 'locked_since': response.json['result']['locked_since'],
380 'locked_since': response.json['result']['locked_since'],
381 'locked_by': self.TEST_USER_LOGIN,
381 'locked_by': self.TEST_USER_LOGIN,
382 'lock_state_changed': True,
382 'lock_state_changed': True,
383 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
383 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
384 % (self.TEST_USER_LOGIN, repo_name, True))
384 % (self.TEST_USER_LOGIN, repo_name, True))
385 }
385 }
386 self._compare_ok(id_, expected, given=response.body)
386 self._compare_ok(id_, expected, given=response.body)
387 finally:
387 finally:
388 fixture.destroy_repo(repo_name)
388 fixture.destroy_repo(repo_name)
389
389
390 def test_api_lock_repo_lock_acquire_non_admin_with_userid(self):
390 def test_api_lock_repo_lock_acquire_non_admin_with_userid(self):
391 repo_name = u'api_delete_me'
391 repo_name = u'api_delete_me'
392 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
392 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
393 cur_user=self.TEST_USER_LOGIN)
393 cur_user=self.TEST_USER_LOGIN)
394 try:
394 try:
395 id_, params = _build_data(self.apikey_regular, 'lock',
395 id_, params = _build_data(self.apikey_regular, 'lock',
396 userid=TEST_USER_ADMIN_LOGIN,
396 userid=TEST_USER_ADMIN_LOGIN,
397 repoid=repo_name,
397 repoid=repo_name,
398 locked=True)
398 locked=True)
399 response = api_call(self, params)
399 response = api_call(self, params)
400 expected = 'userid is not the same as your user'
400 expected = 'userid is not the same as your user'
401 self._compare_error(id_, expected, given=response.body)
401 self._compare_error(id_, expected, given=response.body)
402 finally:
402 finally:
403 fixture.destroy_repo(repo_name)
403 fixture.destroy_repo(repo_name)
404
404
405 def test_api_lock_repo_lock_acquire_non_admin_not_his_repo(self):
405 def test_api_lock_repo_lock_acquire_non_admin_not_his_repo(self):
406 id_, params = _build_data(self.apikey_regular, 'lock',
406 id_, params = _build_data(self.apikey_regular, 'lock',
407 repoid=self.REPO,
407 repoid=self.REPO,
408 locked=True)
408 locked=True)
409 response = api_call(self, params)
409 response = api_call(self, params)
410 expected = 'repository `%s` does not exist' % (self.REPO)
410 expected = 'repository `%s` does not exist' % (self.REPO)
411 self._compare_error(id_, expected, given=response.body)
411 self._compare_error(id_, expected, given=response.body)
412
412
413 def test_api_lock_repo_lock_release(self):
413 def test_api_lock_repo_lock_release(self):
414 id_, params = _build_data(self.apikey, 'lock',
414 id_, params = _build_data(self.apikey, 'lock',
415 userid=TEST_USER_ADMIN_LOGIN,
415 userid=TEST_USER_ADMIN_LOGIN,
416 repoid=self.REPO,
416 repoid=self.REPO,
417 locked=False)
417 locked=False)
418 response = api_call(self, params)
418 response = api_call(self, params)
419 expected = {
419 expected = {
420 'repo': self.REPO,
420 'repo': self.REPO,
421 'locked': False,
421 'locked': False,
422 'locked_since': None,
422 'locked_since': None,
423 'locked_by': TEST_USER_ADMIN_LOGIN,
423 'locked_by': TEST_USER_ADMIN_LOGIN,
424 'lock_state_changed': True,
424 'lock_state_changed': True,
425 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
425 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
426 % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
426 % (TEST_USER_ADMIN_LOGIN, self.REPO, False))
427 }
427 }
428 self._compare_ok(id_, expected, given=response.body)
428 self._compare_ok(id_, expected, given=response.body)
429
429
430 def test_api_lock_repo_lock_acquire_optional_userid(self):
430 def test_api_lock_repo_lock_acquire_optional_userid(self):
431 id_, params = _build_data(self.apikey, 'lock',
431 id_, params = _build_data(self.apikey, 'lock',
432 repoid=self.REPO,
432 repoid=self.REPO,
433 locked=True)
433 locked=True)
434 response = api_call(self, params)
434 response = api_call(self, params)
435 time_ = response.json['result']['locked_since']
435 time_ = response.json['result']['locked_since']
436 expected = {
436 expected = {
437 'repo': self.REPO,
437 'repo': self.REPO,
438 'locked': True,
438 'locked': True,
439 'locked_since': time_,
439 'locked_since': time_,
440 'locked_by': TEST_USER_ADMIN_LOGIN,
440 'locked_by': TEST_USER_ADMIN_LOGIN,
441 'lock_state_changed': True,
441 'lock_state_changed': True,
442 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
442 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
443 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
443 % (TEST_USER_ADMIN_LOGIN, self.REPO, True))
444 }
444 }
445
445
446 self._compare_ok(id_, expected, given=response.body)
446 self._compare_ok(id_, expected, given=response.body)
447
447
448 def test_api_lock_repo_lock_optional_locked(self):
448 def test_api_lock_repo_lock_optional_locked(self):
449 try:
449 try:
450 id_, params = _build_data(self.apikey, 'lock',
450 id_, params = _build_data(self.apikey, 'lock',
451 repoid=self.REPO)
451 repoid=self.REPO)
452 response = api_call(self, params)
452 response = api_call(self, params)
453 time_ = response.json['result']['locked_since']
453 time_ = response.json['result']['locked_since']
454 expected = {
454 expected = {
455 'repo': self.REPO,
455 'repo': self.REPO,
456 'locked': True,
456 'locked': True,
457 'locked_since': time_,
457 'locked_since': time_,
458 'locked_by': TEST_USER_ADMIN_LOGIN,
458 'locked_by': TEST_USER_ADMIN_LOGIN,
459 'lock_state_changed': False,
459 'lock_state_changed': False,
460 'msg': ('Repo `%s` locked by `%s` on `%s`.'
460 'msg': ('Repo `%s` locked by `%s` on `%s`.'
461 % (self.REPO, TEST_USER_ADMIN_LOGIN,
461 % (self.REPO, TEST_USER_ADMIN_LOGIN,
462 json.dumps(time_to_datetime(time_))))
462 json.dumps(time_to_datetime(time_))))
463 }
463 }
464 self._compare_ok(id_, expected, given=response.body)
464 self._compare_ok(id_, expected, given=response.body)
465 finally:
465 finally:
466 # cleanup
466 # cleanup
467 Repository.unlock(RepoModel().get_by_repo_name(self.REPO))
467 Repository.unlock(RepoModel().get_by_repo_name(self.REPO))
468
468
469 def test_api_lock_repo_lock_optional_not_locked(self):
469 def test_api_lock_repo_lock_optional_not_locked(self):
470 repo_name = u'api_not_locked'
470 repo_name = u'api_not_locked'
471 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
471 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
472 cur_user=self.TEST_USER_LOGIN)
472 cur_user=self.TEST_USER_LOGIN)
473 assert repo.locked == [None, None]
473 assert repo.locked == [None, None]
474 try:
474 try:
475 id_, params = _build_data(self.apikey, 'lock',
475 id_, params = _build_data(self.apikey, 'lock',
476 repoid=repo.repo_id)
476 repoid=repo.repo_id)
477 response = api_call(self, params)
477 response = api_call(self, params)
478 expected = {
478 expected = {
479 'repo': repo_name,
479 'repo': repo_name,
480 'locked': False,
480 'locked': False,
481 'locked_since': None,
481 'locked_since': None,
482 'locked_by': None,
482 'locked_by': None,
483 'lock_state_changed': False,
483 'lock_state_changed': False,
484 'msg': ('Repo `%s` not locked.' % (repo_name,))
484 'msg': ('Repo `%s` not locked.' % (repo_name,))
485 }
485 }
486 self._compare_ok(id_, expected, given=response.body)
486 self._compare_ok(id_, expected, given=response.body)
487 finally:
487 finally:
488 fixture.destroy_repo(repo_name)
488 fixture.destroy_repo(repo_name)
489
489
490 @mock.patch.object(Repository, 'lock', crash)
490 @mock.patch.object(Repository, 'lock', crash)
491 def test_api_lock_error(self):
491 def test_api_lock_error(self):
492 id_, params = _build_data(self.apikey, 'lock',
492 id_, params = _build_data(self.apikey, 'lock',
493 userid=TEST_USER_ADMIN_LOGIN,
493 userid=TEST_USER_ADMIN_LOGIN,
494 repoid=self.REPO,
494 repoid=self.REPO,
495 locked=True)
495 locked=True)
496 response = api_call(self, params)
496 response = api_call(self, params)
497
497
498 expected = 'Error occurred locking repository `%s`' % self.REPO
498 expected = 'Error occurred locking repository `%s`' % self.REPO
499 self._compare_error(id_, expected, given=response.body)
499 self._compare_error(id_, expected, given=response.body)
500
500
501 def test_api_get_locks_regular_user(self):
501 def test_api_get_locks_regular_user(self):
502 id_, params = _build_data(self.apikey_regular, 'get_locks')
502 id_, params = _build_data(self.apikey_regular, 'get_locks')
503 response = api_call(self, params)
503 response = api_call(self, params)
504 expected = []
504 expected = []
505 self._compare_ok(id_, expected, given=response.body)
505 self._compare_ok(id_, expected, given=response.body)
506
506
507 def test_api_get_locks_with_userid_regular_user(self):
507 def test_api_get_locks_with_userid_regular_user(self):
508 id_, params = _build_data(self.apikey_regular, 'get_locks',
508 id_, params = _build_data(self.apikey_regular, 'get_locks',
509 userid=TEST_USER_ADMIN_LOGIN)
509 userid=TEST_USER_ADMIN_LOGIN)
510 response = api_call(self, params)
510 response = api_call(self, params)
511 expected = 'userid is not the same as your user'
511 expected = 'userid is not the same as your user'
512 self._compare_error(id_, expected, given=response.body)
512 self._compare_error(id_, expected, given=response.body)
513
513
514 def test_api_get_locks(self):
514 def test_api_get_locks(self):
515 id_, params = _build_data(self.apikey, 'get_locks')
515 id_, params = _build_data(self.apikey, 'get_locks')
516 response = api_call(self, params)
516 response = api_call(self, params)
517 expected = []
517 expected = []
518 self._compare_ok(id_, expected, given=response.body)
518 self._compare_ok(id_, expected, given=response.body)
519
519
520 def test_api_get_locks_with_one_locked_repo(self):
520 def test_api_get_locks_with_one_locked_repo(self):
521 repo_name = u'api_delete_me'
521 repo_name = u'api_delete_me'
522 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
522 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
523 cur_user=self.TEST_USER_LOGIN)
523 cur_user=self.TEST_USER_LOGIN)
524 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
524 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
525 try:
525 try:
526 id_, params = _build_data(self.apikey, 'get_locks')
526 id_, params = _build_data(self.apikey, 'get_locks')
527 response = api_call(self, params)
527 response = api_call(self, params)
528 expected = [repo.get_api_data()]
528 expected = [repo.get_api_data()]
529 self._compare_ok(id_, expected, given=response.body)
529 self._compare_ok(id_, expected, given=response.body)
530 finally:
530 finally:
531 fixture.destroy_repo(repo_name)
531 fixture.destroy_repo(repo_name)
532
532
533 def test_api_get_locks_with_one_locked_repo_for_specific_user(self):
533 def test_api_get_locks_with_one_locked_repo_for_specific_user(self):
534 repo_name = u'api_delete_me'
534 repo_name = u'api_delete_me'
535 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
535 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
536 cur_user=self.TEST_USER_LOGIN)
536 cur_user=self.TEST_USER_LOGIN)
537 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
537 Repository.lock(repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
538 try:
538 try:
539 id_, params = _build_data(self.apikey, 'get_locks',
539 id_, params = _build_data(self.apikey, 'get_locks',
540 userid=self.TEST_USER_LOGIN)
540 userid=self.TEST_USER_LOGIN)
541 response = api_call(self, params)
541 response = api_call(self, params)
542 expected = [repo.get_api_data()]
542 expected = [repo.get_api_data()]
543 self._compare_ok(id_, expected, given=response.body)
543 self._compare_ok(id_, expected, given=response.body)
544 finally:
544 finally:
545 fixture.destroy_repo(repo_name)
545 fixture.destroy_repo(repo_name)
546
546
547 def test_api_get_locks_with_userid(self):
547 def test_api_get_locks_with_userid(self):
548 id_, params = _build_data(self.apikey, 'get_locks',
548 id_, params = _build_data(self.apikey, 'get_locks',
549 userid=TEST_USER_REGULAR_LOGIN)
549 userid=TEST_USER_REGULAR_LOGIN)
550 response = api_call(self, params)
550 response = api_call(self, params)
551 expected = []
551 expected = []
552 self._compare_ok(id_, expected, given=response.body)
552 self._compare_ok(id_, expected, given=response.body)
553
553
554 def test_api_create_existing_user(self):
554 def test_api_create_existing_user(self):
555 id_, params = _build_data(self.apikey, 'create_user',
555 id_, params = _build_data(self.apikey, 'create_user',
556 username=TEST_USER_ADMIN_LOGIN,
556 username=TEST_USER_ADMIN_LOGIN,
557 email='test@example.com',
557 email='test@example.com',
558 password='trololo')
558 password='trololo')
559 response = api_call(self, params)
559 response = api_call(self, params)
560
560
561 expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN
561 expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN
562 self._compare_error(id_, expected, given=response.body)
562 self._compare_error(id_, expected, given=response.body)
563
563
564 def test_api_create_user_with_existing_email(self):
564 def test_api_create_user_with_existing_email(self):
565 id_, params = _build_data(self.apikey, 'create_user',
565 id_, params = _build_data(self.apikey, 'create_user',
566 username=TEST_USER_ADMIN_LOGIN + 'new',
566 username=TEST_USER_ADMIN_LOGIN + 'new',
567 email=TEST_USER_REGULAR_EMAIL,
567 email=TEST_USER_REGULAR_EMAIL,
568 password='trololo')
568 password='trololo')
569 response = api_call(self, params)
569 response = api_call(self, params)
570
570
571 expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL
571 expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL
572 self._compare_error(id_, expected, given=response.body)
572 self._compare_error(id_, expected, given=response.body)
573
573
574 def test_api_create_user(self):
574 def test_api_create_user(self):
575 username = 'test_new_api_user'
575 username = 'test_new_api_user'
576 email = username + "@example.com"
576 email = username + "@example.com"
577
577
578 id_, params = _build_data(self.apikey, 'create_user',
578 id_, params = _build_data(self.apikey, 'create_user',
579 username=username,
579 username=username,
580 email=email,
580 email=email,
581 password='trololo')
581 password='trololo')
582 response = api_call(self, params)
582 response = api_call(self, params)
583
583
584 usr = User.get_by_username(username)
584 usr = User.get_by_username(username)
585 ret = dict(
585 ret = dict(
586 msg='created new user `%s`' % username,
586 msg='created new user `%s`' % username,
587 user=jsonify(usr.get_api_data())
587 user=jsonify(usr.get_api_data())
588 )
588 )
589
589
590 try:
590 try:
591 expected = ret
591 expected = ret
592 self._compare_ok(id_, expected, given=response.body)
592 self._compare_ok(id_, expected, given=response.body)
593 finally:
593 finally:
594 fixture.destroy_user(usr.user_id)
594 fixture.destroy_user(usr.user_id)
595
595
596 def test_api_create_user_without_password(self):
596 def test_api_create_user_without_password(self):
597 username = 'test_new_api_user_passwordless'
597 username = 'test_new_api_user_passwordless'
598 email = username + "@example.com"
598 email = username + "@example.com"
599
599
600 id_, params = _build_data(self.apikey, 'create_user',
600 id_, params = _build_data(self.apikey, 'create_user',
601 username=username,
601 username=username,
602 email=email)
602 email=email)
603 response = api_call(self, params)
603 response = api_call(self, params)
604
604
605 usr = User.get_by_username(username)
605 usr = User.get_by_username(username)
606 ret = dict(
606 ret = dict(
607 msg='created new user `%s`' % username,
607 msg='created new user `%s`' % username,
608 user=jsonify(usr.get_api_data())
608 user=jsonify(usr.get_api_data())
609 )
609 )
610 try:
610 try:
611 expected = ret
611 expected = ret
612 self._compare_ok(id_, expected, given=response.body)
612 self._compare_ok(id_, expected, given=response.body)
613 finally:
613 finally:
614 fixture.destroy_user(usr.user_id)
614 fixture.destroy_user(usr.user_id)
615
615
616 def test_api_create_user_with_extern_name(self):
616 def test_api_create_user_with_extern_name(self):
617 username = 'test_new_api_user_passwordless'
617 username = 'test_new_api_user_passwordless'
618 email = username + "@example.com"
618 email = username + "@example.com"
619
619
620 id_, params = _build_data(self.apikey, 'create_user',
620 id_, params = _build_data(self.apikey, 'create_user',
621 username=username,
621 username=username,
622 email=email, extern_name='internal')
622 email=email, extern_name='internal')
623 response = api_call(self, params)
623 response = api_call(self, params)
624
624
625 usr = User.get_by_username(username)
625 usr = User.get_by_username(username)
626 ret = dict(
626 ret = dict(
627 msg='created new user `%s`' % username,
627 msg='created new user `%s`' % username,
628 user=jsonify(usr.get_api_data())
628 user=jsonify(usr.get_api_data())
629 )
629 )
630 try:
630 try:
631 expected = ret
631 expected = ret
632 self._compare_ok(id_, expected, given=response.body)
632 self._compare_ok(id_, expected, given=response.body)
633 finally:
633 finally:
634 fixture.destroy_user(usr.user_id)
634 fixture.destroy_user(usr.user_id)
635
635
636 @mock.patch.object(UserModel, 'create_or_update', crash)
636 @mock.patch.object(UserModel, 'create_or_update', crash)
637 def test_api_create_user_when_exception_happened(self):
637 def test_api_create_user_when_exception_happened(self):
638
638
639 username = 'test_new_api_user'
639 username = 'test_new_api_user'
640 email = username + "@example.com"
640 email = username + "@example.com"
641
641
642 id_, params = _build_data(self.apikey, 'create_user',
642 id_, params = _build_data(self.apikey, 'create_user',
643 username=username,
643 username=username,
644 email=email,
644 email=email,
645 password='trololo')
645 password='trololo')
646 response = api_call(self, params)
646 response = api_call(self, params)
647 expected = 'failed to create user `%s`' % username
647 expected = 'failed to create user `%s`' % username
648 self._compare_error(id_, expected, given=response.body)
648 self._compare_error(id_, expected, given=response.body)
649
649
650 def test_api_delete_user(self):
650 def test_api_delete_user(self):
651 usr = UserModel().create_or_update(username=u'test_user',
651 usr = UserModel().create_or_update(username=u'test_user',
652 password=u'qweqwe',
652 password=u'qweqwe',
653 email=u'u232@example.com',
653 email=u'u232@example.com',
654 firstname=u'u1', lastname=u'u1')
654 firstname=u'u1', lastname=u'u1')
655 Session().commit()
655 Session().commit()
656 username = usr.username
656 username = usr.username
657 email = usr.email
657 email = usr.email
658 usr_id = usr.user_id
658 usr_id = usr.user_id
659 ## DELETE THIS USER NOW
659 ## DELETE THIS USER NOW
660
660
661 id_, params = _build_data(self.apikey, 'delete_user',
661 id_, params = _build_data(self.apikey, 'delete_user',
662 userid=username, )
662 userid=username, )
663 response = api_call(self, params)
663 response = api_call(self, params)
664
664
665 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
665 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
666 'user': None}
666 'user': None}
667 expected = ret
667 expected = ret
668 self._compare_ok(id_, expected, given=response.body)
668 self._compare_ok(id_, expected, given=response.body)
669
669
670 @mock.patch.object(UserModel, 'delete', crash)
670 @mock.patch.object(UserModel, 'delete', crash)
671 def test_api_delete_user_when_exception_happened(self):
671 def test_api_delete_user_when_exception_happened(self):
672 usr = UserModel().create_or_update(username=u'test_user',
672 usr = UserModel().create_or_update(username=u'test_user',
673 password=u'qweqwe',
673 password=u'qweqwe',
674 email=u'u232@example.com',
674 email=u'u232@example.com',
675 firstname=u'u1', lastname=u'u1')
675 firstname=u'u1', lastname=u'u1')
676 Session().commit()
676 Session().commit()
677 username = usr.username
677 username = usr.username
678
678
679 id_, params = _build_data(self.apikey, 'delete_user',
679 id_, params = _build_data(self.apikey, 'delete_user',
680 userid=username, )
680 userid=username, )
681 response = api_call(self, params)
681 response = api_call(self, params)
682 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
682 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
683 usr.username)
683 usr.username)
684 expected = ret
684 expected = ret
685 self._compare_error(id_, expected, given=response.body)
685 self._compare_error(id_, expected, given=response.body)
686
686
687 @parametrize('name,expected', [
687 @parametrize('name,expected', [
688 ('firstname', 'new_username'),
688 ('firstname', 'new_username'),
689 ('lastname', 'new_username'),
689 ('lastname', 'new_username'),
690 ('email', 'new_username'),
690 ('email', 'new_username'),
691 ('admin', True),
691 ('admin', True),
692 ('admin', False),
692 ('admin', False),
693 ('extern_type', 'ldap'),
693 ('extern_type', 'ldap'),
694 ('extern_type', None),
694 ('extern_type', None),
695 ('extern_name', 'test'),
695 ('extern_name', 'test'),
696 ('extern_name', None),
696 ('extern_name', None),
697 ('active', False),
697 ('active', False),
698 ('active', True),
698 ('active', True),
699 ('password', 'newpass'),
699 ('password', 'newpass'),
700 ])
700 ])
701 def test_api_update_user(self, name, expected):
701 def test_api_update_user(self, name, expected):
702 usr = User.get_by_username(self.TEST_USER_LOGIN)
702 usr = User.get_by_username(self.TEST_USER_LOGIN)
703 kw = {name: expected,
703 kw = {name: expected,
704 'userid': usr.user_id}
704 'userid': usr.user_id}
705 id_, params = _build_data(self.apikey, 'update_user', **kw)
705 id_, params = _build_data(self.apikey, 'update_user', **kw)
706 response = api_call(self, params)
706 response = api_call(self, params)
707
707
708 ret = {
708 ret = {
709 'msg': 'updated user ID:%s %s' % (
709 'msg': 'updated user ID:%s %s' % (
710 usr.user_id, self.TEST_USER_LOGIN),
710 usr.user_id, self.TEST_USER_LOGIN),
711 'user': jsonify(User \
711 'user': jsonify(User \
712 .get_by_username(self.TEST_USER_LOGIN) \
712 .get_by_username(self.TEST_USER_LOGIN) \
713 .get_api_data())
713 .get_api_data())
714 }
714 }
715
715
716 expected = ret
716 expected = ret
717 self._compare_ok(id_, expected, given=response.body)
717 self._compare_ok(id_, expected, given=response.body)
718
718
719 def test_api_update_user_no_changed_params(self):
719 def test_api_update_user_no_changed_params(self):
720 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
720 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
721 ret = jsonify(usr.get_api_data())
721 ret = jsonify(usr.get_api_data())
722 id_, params = _build_data(self.apikey, 'update_user',
722 id_, params = _build_data(self.apikey, 'update_user',
723 userid=TEST_USER_ADMIN_LOGIN)
723 userid=TEST_USER_ADMIN_LOGIN)
724
724
725 response = api_call(self, params)
725 response = api_call(self, params)
726 ret = {
726 ret = {
727 'msg': 'updated user ID:%s %s' % (
727 'msg': 'updated user ID:%s %s' % (
728 usr.user_id, TEST_USER_ADMIN_LOGIN),
728 usr.user_id, TEST_USER_ADMIN_LOGIN),
729 'user': ret
729 'user': ret
730 }
730 }
731 expected = ret
731 expected = ret
732 self._compare_ok(id_, expected, given=response.body)
732 self._compare_ok(id_, expected, given=response.body)
733
733
734 def test_api_update_user_by_user_id(self):
734 def test_api_update_user_by_user_id(self):
735 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
735 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
736 ret = jsonify(usr.get_api_data())
736 ret = jsonify(usr.get_api_data())
737 id_, params = _build_data(self.apikey, 'update_user',
737 id_, params = _build_data(self.apikey, 'update_user',
738 userid=usr.user_id)
738 userid=usr.user_id)
739
739
740 response = api_call(self, params)
740 response = api_call(self, params)
741 ret = {
741 ret = {
742 'msg': 'updated user ID:%s %s' % (
742 'msg': 'updated user ID:%s %s' % (
743 usr.user_id, TEST_USER_ADMIN_LOGIN),
743 usr.user_id, TEST_USER_ADMIN_LOGIN),
744 'user': ret
744 'user': ret
745 }
745 }
746 expected = ret
746 expected = ret
747 self._compare_ok(id_, expected, given=response.body)
747 self._compare_ok(id_, expected, given=response.body)
748
748
749 def test_api_update_user_default_user(self):
749 def test_api_update_user_default_user(self):
750 usr = User.get_default_user()
750 usr = User.get_default_user()
751 id_, params = _build_data(self.apikey, 'update_user',
751 id_, params = _build_data(self.apikey, 'update_user',
752 userid=usr.user_id)
752 userid=usr.user_id)
753
753
754 response = api_call(self, params)
754 response = api_call(self, params)
755 expected = 'editing default user is forbidden'
755 expected = 'editing default user is forbidden'
756 self._compare_error(id_, expected, given=response.body)
756 self._compare_error(id_, expected, given=response.body)
757
757
758 @mock.patch.object(UserModel, 'update_user', crash)
758 @mock.patch.object(UserModel, 'update_user', crash)
759 def test_api_update_user_when_exception_happens(self):
759 def test_api_update_user_when_exception_happens(self):
760 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
760 usr = User.get_by_username(TEST_USER_ADMIN_LOGIN)
761 ret = jsonify(usr.get_api_data())
761 ret = jsonify(usr.get_api_data())
762 id_, params = _build_data(self.apikey, 'update_user',
762 id_, params = _build_data(self.apikey, 'update_user',
763 userid=usr.user_id)
763 userid=usr.user_id)
764
764
765 response = api_call(self, params)
765 response = api_call(self, params)
766 ret = 'failed to update user `%s`' % usr.user_id
766 ret = 'failed to update user `%s`' % usr.user_id
767
767
768 expected = ret
768 expected = ret
769 self._compare_error(id_, expected, given=response.body)
769 self._compare_error(id_, expected, given=response.body)
770
770
771 def test_api_get_repo(self):
771 def test_api_get_repo(self):
772 new_group = u'some_new_group'
772 new_group = u'some_new_group'
773 make_user_group(new_group)
773 make_user_group(new_group)
774 RepoModel().grant_user_group_permission(repo=self.REPO,
774 RepoModel().grant_user_group_permission(repo=self.REPO,
775 group_name=new_group,
775 group_name=new_group,
776 perm='repository.read')
776 perm='repository.read')
777 Session().commit()
777 Session().commit()
778 id_, params = _build_data(self.apikey, 'get_repo',
778 id_, params = _build_data(self.apikey, 'get_repo',
779 repoid=self.REPO)
779 repoid=self.REPO)
780 response = api_call(self, params)
780 response = api_call(self, params)
781 assert u"tags" not in response.json[u'result']
781 assert u"tags" not in response.json[u'result']
782 assert u'pull_requests' not in response.json[u'result']
782 assert u'pull_requests' not in response.json[u'result']
783
783
784 repo = RepoModel().get_by_repo_name(self.REPO)
784 repo = RepoModel().get_by_repo_name(self.REPO)
785 ret = repo.get_api_data()
785 ret = repo.get_api_data()
786
786
787 members = []
787 members = []
788 followers = []
788 followers = []
789 for user in repo.repo_to_perm:
789 for user in repo.repo_to_perm:
790 perm = user.permission.permission_name
790 perm = user.permission.permission_name
791 user = user.user
791 user = user.user
792 user_data = {'name': user.username, 'type': "user",
792 user_data = {'name': user.username, 'type': "user",
793 'permission': perm}
793 'permission': perm}
794 members.append(user_data)
794 members.append(user_data)
795
795
796 for user_group in repo.users_group_to_perm:
796 for user_group in repo.users_group_to_perm:
797 perm = user_group.permission.permission_name
797 perm = user_group.permission.permission_name
798 user_group = user_group.users_group
798 user_group = user_group.users_group
799 user_group_data = {'name': user_group.users_group_name,
799 user_group_data = {'name': user_group.users_group_name,
800 'type': "user_group", 'permission': perm}
800 'type': "user_group", 'permission': perm}
801 members.append(user_group_data)
801 members.append(user_group_data)
802
802
803 for user in repo.followers:
803 for user in repo.followers:
804 followers.append(user.user.get_api_data())
804 followers.append(user.user.get_api_data())
805
805
806 ret['members'] = members
806 ret['members'] = members
807 ret['followers'] = followers
807 ret['followers'] = followers
808
808
809 expected = ret
809 expected = ret
810 self._compare_ok(id_, expected, given=response.body)
810 self._compare_ok(id_, expected, given=response.body)
811 fixture.destroy_user_group(new_group)
811 fixture.destroy_user_group(new_group)
812
812
813 id_, params = _build_data(self.apikey, 'get_repo', repoid=self.REPO,
813 id_, params = _build_data(self.apikey, 'get_repo', repoid=self.REPO,
814 with_revision_names=True,
814 with_revision_names=True,
815 with_pullrequests=True)
815 with_pullrequests=True)
816 response = api_call(self, params)
816 response = api_call(self, params)
817 assert u"v0.2.0" in response.json[u'result'][u'tags']
817 assert u"v0.2.0" in response.json[u'result'][u'tags']
818 assert u'pull_requests' in response.json[u'result']
818 assert u'pull_requests' in response.json[u'result']
819
819
820 @parametrize('grant_perm', [
820 @parametrize('grant_perm', [
821 ('repository.admin'),
821 ('repository.admin'),
822 ('repository.write'),
822 ('repository.write'),
823 ('repository.read'),
823 ('repository.read'),
824 ])
824 ])
825 def test_api_get_repo_by_non_admin(self, grant_perm):
825 def test_api_get_repo_by_non_admin(self, grant_perm):
826 RepoModel().grant_user_permission(repo=self.REPO,
826 RepoModel().grant_user_permission(repo=self.REPO,
827 user=self.TEST_USER_LOGIN,
827 user=self.TEST_USER_LOGIN,
828 perm=grant_perm)
828 perm=grant_perm)
829 Session().commit()
829 Session().commit()
830 id_, params = _build_data(self.apikey_regular, 'get_repo',
830 id_, params = _build_data(self.apikey_regular, 'get_repo',
831 repoid=self.REPO)
831 repoid=self.REPO)
832 response = api_call(self, params)
832 response = api_call(self, params)
833
833
834 repo = RepoModel().get_by_repo_name(self.REPO)
834 repo = RepoModel().get_by_repo_name(self.REPO)
835 ret = repo.get_api_data()
835 ret = repo.get_api_data()
836
836
837 members = []
837 members = []
838 followers = []
838 followers = []
839 assert 2 == len(repo.repo_to_perm)
839 assert 2 == len(repo.repo_to_perm)
840 for user in repo.repo_to_perm:
840 for user in repo.repo_to_perm:
841 perm = user.permission.permission_name
841 perm = user.permission.permission_name
842 user_obj = user.user
842 user_obj = user.user
843 user_data = {'name': user_obj.username, 'type': "user",
843 user_data = {'name': user_obj.username, 'type': "user",
844 'permission': perm}
844 'permission': perm}
845 members.append(user_data)
845 members.append(user_data)
846
846
847 for user_group in repo.users_group_to_perm:
847 for user_group in repo.users_group_to_perm:
848 perm = user_group.permission.permission_name
848 perm = user_group.permission.permission_name
849 user_group_obj = user_group.users_group
849 user_group_obj = user_group.users_group
850 user_group_data = {'name': user_group_obj.users_group_name,
850 user_group_data = {'name': user_group_obj.users_group_name,
851 'type': "user_group", 'permission': perm}
851 'type': "user_group", 'permission': perm}
852 members.append(user_group_data)
852 members.append(user_group_data)
853
853
854 for user in repo.followers:
854 for user in repo.followers:
855 followers.append(user.user.get_api_data())
855 followers.append(user.user.get_api_data())
856
856
857 ret['members'] = members
857 ret['members'] = members
858 ret['followers'] = followers
858 ret['followers'] = followers
859
859
860 expected = ret
860 expected = ret
861 try:
861 try:
862 self._compare_ok(id_, expected, given=response.body)
862 self._compare_ok(id_, expected, given=response.body)
863 finally:
863 finally:
864 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
864 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
865
865
866 def test_api_get_repo_by_non_admin_no_permission_to_repo(self):
866 def test_api_get_repo_by_non_admin_no_permission_to_repo(self):
867 RepoModel().grant_user_permission(repo=self.REPO,
867 RepoModel().grant_user_permission(repo=self.REPO,
868 user=self.TEST_USER_LOGIN,
868 user=self.TEST_USER_LOGIN,
869 perm='repository.none')
869 perm='repository.none')
870
870
871 id_, params = _build_data(self.apikey_regular, 'get_repo',
871 id_, params = _build_data(self.apikey_regular, 'get_repo',
872 repoid=self.REPO)
872 repoid=self.REPO)
873 response = api_call(self, params)
873 response = api_call(self, params)
874
874
875 expected = 'repository `%s` does not exist' % (self.REPO)
875 expected = 'repository `%s` does not exist' % (self.REPO)
876 self._compare_error(id_, expected, given=response.body)
876 self._compare_error(id_, expected, given=response.body)
877
877
878 def test_api_get_repo_that_doesn_not_exist(self):
878 def test_api_get_repo_that_doesn_not_exist(self):
879 id_, params = _build_data(self.apikey, 'get_repo',
879 id_, params = _build_data(self.apikey, 'get_repo',
880 repoid='no-such-repo')
880 repoid='no-such-repo')
881 response = api_call(self, params)
881 response = api_call(self, params)
882
882
883 ret = 'repository `%s` does not exist' % 'no-such-repo'
883 ret = 'repository `%s` does not exist' % 'no-such-repo'
884 expected = ret
884 expected = ret
885 self._compare_error(id_, expected, given=response.body)
885 self._compare_error(id_, expected, given=response.body)
886
886
887 def test_api_get_repos(self):
887 def test_api_get_repos(self):
888 id_, params = _build_data(self.apikey, 'get_repos')
888 id_, params = _build_data(self.apikey, 'get_repos')
889 response = api_call(self, params)
889 response = api_call(self, params)
890
890
891 expected = jsonify([
891 expected = jsonify([
892 repo.get_api_data()
892 repo.get_api_data()
893 for repo in Repository.query()
893 for repo in Repository.query()
894 ])
894 ])
895
895
896 self._compare_ok(id_, expected, given=response.body)
896 self._compare_ok(id_, expected, given=response.body)
897
897
898 def test_api_get_repos_non_admin(self):
898 def test_api_get_repos_non_admin(self):
899 id_, params = _build_data(self.apikey_regular, 'get_repos')
899 id_, params = _build_data(self.apikey_regular, 'get_repos')
900 response = api_call(self, params)
900 response = api_call(self, params)
901
901
902 expected = jsonify([
902 expected = jsonify([
903 repo.get_api_data()
903 repo.get_api_data()
904 for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN)
904 for repo in RepoModel().get_all_user_repos(self.TEST_USER_LOGIN)
905 ])
905 ])
906
906
907 self._compare_ok(id_, expected, given=response.body)
907 self._compare_ok(id_, expected, given=response.body)
908
908
909 @parametrize('name,ret_type', [
909 @parametrize('name,ret_type', [
910 ('all', 'all'),
910 ('all', 'all'),
911 ('dirs', 'dirs'),
911 ('dirs', 'dirs'),
912 ('files', 'files'),
912 ('files', 'files'),
913 ])
913 ])
914 def test_api_get_repo_nodes(self, name, ret_type):
914 def test_api_get_repo_nodes(self, name, ret_type):
915 rev = 'tip'
915 rev = 'tip'
916 path = '/'
916 path = '/'
917 id_, params = _build_data(self.apikey, 'get_repo_nodes',
917 id_, params = _build_data(self.apikey, 'get_repo_nodes',
918 repoid=self.REPO, revision=rev,
918 repoid=self.REPO, revision=rev,
919 root_path=path,
919 root_path=path,
920 ret_type=ret_type)
920 ret_type=ret_type)
921 response = api_call(self, params)
921 response = api_call(self, params)
922
922
923 # we don't the actual return types here since it's tested somewhere
923 # we don't the actual return types here since it's tested somewhere
924 # else
924 # else
925 expected = response.json['result']
925 expected = response.json['result']
926 self._compare_ok(id_, expected, given=response.body)
926 self._compare_ok(id_, expected, given=response.body)
927
927
928 def test_api_get_repo_nodes_bad_revisions(self):
928 def test_api_get_repo_nodes_bad_revisions(self):
929 rev = 'i-dont-exist'
929 rev = 'i-dont-exist'
930 path = '/'
930 path = '/'
931 id_, params = _build_data(self.apikey, 'get_repo_nodes',
931 id_, params = _build_data(self.apikey, 'get_repo_nodes',
932 repoid=self.REPO, revision=rev,
932 repoid=self.REPO, revision=rev,
933 root_path=path, )
933 root_path=path, )
934 response = api_call(self, params)
934 response = api_call(self, params)
935
935
936 expected = 'failed to get repo: `%s` nodes' % self.REPO
936 expected = 'failed to get repo: `%s` nodes' % self.REPO
937 self._compare_error(id_, expected, given=response.body)
937 self._compare_error(id_, expected, given=response.body)
938
938
939 def test_api_get_repo_nodes_bad_path(self):
939 def test_api_get_repo_nodes_bad_path(self):
940 rev = 'tip'
940 rev = 'tip'
941 path = '/idontexits'
941 path = '/idontexits'
942 id_, params = _build_data(self.apikey, 'get_repo_nodes',
942 id_, params = _build_data(self.apikey, 'get_repo_nodes',
943 repoid=self.REPO, revision=rev,
943 repoid=self.REPO, revision=rev,
944 root_path=path, )
944 root_path=path, )
945 response = api_call(self, params)
945 response = api_call(self, params)
946
946
947 expected = 'failed to get repo: `%s` nodes' % self.REPO
947 expected = 'failed to get repo: `%s` nodes' % self.REPO
948 self._compare_error(id_, expected, given=response.body)
948 self._compare_error(id_, expected, given=response.body)
949
949
950 def test_api_get_repo_nodes_bad_ret_type(self):
950 def test_api_get_repo_nodes_bad_ret_type(self):
951 rev = 'tip'
951 rev = 'tip'
952 path = '/'
952 path = '/'
953 ret_type = 'error'
953 ret_type = 'error'
954 id_, params = _build_data(self.apikey, 'get_repo_nodes',
954 id_, params = _build_data(self.apikey, 'get_repo_nodes',
955 repoid=self.REPO, revision=rev,
955 repoid=self.REPO, revision=rev,
956 root_path=path,
956 root_path=path,
957 ret_type=ret_type)
957 ret_type=ret_type)
958 response = api_call(self, params)
958 response = api_call(self, params)
959
959
960 expected = ('ret_type must be one of %s'
960 expected = ('ret_type must be one of %s'
961 % (','.join(['files', 'dirs', 'all'])))
961 % (','.join(['files', 'dirs', 'all'])))
962 self._compare_error(id_, expected, given=response.body)
962 self._compare_error(id_, expected, given=response.body)
963
963
964 @parametrize('name,ret_type,grant_perm', [
964 @parametrize('name,ret_type,grant_perm', [
965 ('all', 'all', 'repository.write'),
965 ('all', 'all', 'repository.write'),
966 ('dirs', 'dirs', 'repository.admin'),
966 ('dirs', 'dirs', 'repository.admin'),
967 ('files', 'files', 'repository.read'),
967 ('files', 'files', 'repository.read'),
968 ])
968 ])
969 def test_api_get_repo_nodes_by_regular_user(self, name, ret_type, grant_perm):
969 def test_api_get_repo_nodes_by_regular_user(self, name, ret_type, grant_perm):
970 RepoModel().grant_user_permission(repo=self.REPO,
970 RepoModel().grant_user_permission(repo=self.REPO,
971 user=self.TEST_USER_LOGIN,
971 user=self.TEST_USER_LOGIN,
972 perm=grant_perm)
972 perm=grant_perm)
973 Session().commit()
973 Session().commit()
974
974
975 rev = 'tip'
975 rev = 'tip'
976 path = '/'
976 path = '/'
977 id_, params = _build_data(self.apikey_regular, 'get_repo_nodes',
977 id_, params = _build_data(self.apikey_regular, 'get_repo_nodes',
978 repoid=self.REPO, revision=rev,
978 repoid=self.REPO, revision=rev,
979 root_path=path,
979 root_path=path,
980 ret_type=ret_type)
980 ret_type=ret_type)
981 response = api_call(self, params)
981 response = api_call(self, params)
982
982
983 # we don't the actual return types here since it's tested somewhere
983 # we don't the actual return types here since it's tested somewhere
984 # else
984 # else
985 expected = response.json['result']
985 expected = response.json['result']
986 try:
986 try:
987 self._compare_ok(id_, expected, given=response.body)
987 self._compare_ok(id_, expected, given=response.body)
988 finally:
988 finally:
989 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
989 RepoModel().revoke_user_permission(self.REPO, self.TEST_USER_LOGIN)
990
990
991 def test_api_create_repo(self):
991 def test_api_create_repo(self):
992 repo_name = u'api-repo'
992 repo_name = u'api-repo'
993 id_, params = _build_data(self.apikey, 'create_repo',
993 id_, params = _build_data(self.apikey, 'create_repo',
994 repo_name=repo_name,
994 repo_name=repo_name,
995 owner=TEST_USER_ADMIN_LOGIN,
995 owner=TEST_USER_ADMIN_LOGIN,
996 repo_type=self.REPO_TYPE,
996 repo_type=self.REPO_TYPE,
997 )
997 )
998 response = api_call(self, params)
998 response = api_call(self, params)
999
999
1000 repo = RepoModel().get_by_repo_name(repo_name)
1000 repo = RepoModel().get_by_repo_name(repo_name)
1001 assert repo != None
1001 assert repo != None
1002 ret = {
1002 ret = {
1003 'msg': 'Created new repository `%s`' % repo_name,
1003 'msg': 'Created new repository `%s`' % repo_name,
1004 'success': True,
1004 'success': True,
1005 'task': None,
1005 'task': None,
1006 }
1006 }
1007 expected = ret
1007 expected = ret
1008 self._compare_ok(id_, expected, given=response.body)
1008 self._compare_ok(id_, expected, given=response.body)
1009 fixture.destroy_repo(repo_name)
1009 fixture.destroy_repo(repo_name)
1010
1010
1011 def test_api_create_repo_and_repo_group(self):
1011 def test_api_create_repo_and_repo_group(self):
1012 repo_name = u'my_gr/api-repo'
1012 repo_name = u'my_gr/api-repo'
1013 id_, params = _build_data(self.apikey, 'create_repo',
1013 id_, params = _build_data(self.apikey, 'create_repo',
1014 repo_name=repo_name,
1014 repo_name=repo_name,
1015 owner=TEST_USER_ADMIN_LOGIN,
1015 owner=TEST_USER_ADMIN_LOGIN,
1016 repo_type=self.REPO_TYPE,)
1016 repo_type=self.REPO_TYPE,)
1017 response = api_call(self, params)
1017 response = api_call(self, params)
1018 print params
1018 print params
1019 repo = RepoModel().get_by_repo_name(repo_name)
1019 repo = RepoModel().get_by_repo_name(repo_name)
1020 assert repo != None
1020 assert repo != None
1021 ret = {
1021 ret = {
1022 'msg': 'Created new repository `%s`' % repo_name,
1022 'msg': 'Created new repository `%s`' % repo_name,
1023 'success': True,
1023 'success': True,
1024 'task': None,
1024 'task': None,
1025 }
1025 }
1026 expected = ret
1026 expected = ret
1027 self._compare_ok(id_, expected, given=response.body)
1027 self._compare_ok(id_, expected, given=response.body)
1028 fixture.destroy_repo(repo_name)
1028 fixture.destroy_repo(repo_name)
1029 fixture.destroy_repo_group(u'my_gr')
1029 fixture.destroy_repo_group(u'my_gr')
1030
1030
1031 def test_api_create_repo_in_repo_group_without_permission(self):
1031 def test_api_create_repo_in_repo_group_without_permission(self):
1032 repo_group_name = u'%s/api-repo-repo' % TEST_REPO_GROUP
1032 repo_group_name = u'%s/api-repo-repo' % TEST_REPO_GROUP
1033 repo_name = u'%s/api-repo' % repo_group_name
1033 repo_name = u'%s/api-repo' % repo_group_name
1034
1034
1035 rg = fixture.create_repo_group(repo_group_name)
1035 rg = fixture.create_repo_group(repo_group_name)
1036 Session().commit()
1036 Session().commit()
1037 RepoGroupModel().grant_user_permission(repo_group_name,
1037 RepoGroupModel().grant_user_permission(repo_group_name,
1038 self.TEST_USER_LOGIN,
1038 self.TEST_USER_LOGIN,
1039 'group.none')
1039 'group.none')
1040 Session().commit()
1040 Session().commit()
1041
1041
1042 id_, params = _build_data(self.apikey_regular, 'create_repo',
1042 id_, params = _build_data(self.apikey_regular, 'create_repo',
1043 repo_name=repo_name,
1043 repo_name=repo_name,
1044 repo_type=self.REPO_TYPE,
1044 repo_type=self.REPO_TYPE,
1045 )
1045 )
1046 response = api_call(self, params)
1046 response = api_call(self, params)
1047
1047
1048 # Current result when API access control is different from Web:
1048 # Current result when API access control is different from Web:
1049 ret = {
1049 ret = {
1050 'msg': 'Created new repository `%s`' % repo_name,
1050 'msg': 'Created new repository `%s`' % repo_name,
1051 'success': True,
1051 'success': True,
1052 'task': None,
1052 'task': None,
1053 }
1053 }
1054 expected = ret
1054 expected = ret
1055 self._compare_ok(id_, expected, given=response.body)
1055 self._compare_ok(id_, expected, given=response.body)
1056 fixture.destroy_repo(repo_name)
1056 fixture.destroy_repo(repo_name)
1057
1057
1058 # Expected and arguably more correct result:
1058 # Expected and arguably more correct result:
1059 #expected = 'failed to create repository `%s`' % repo_name
1059 #expected = 'failed to create repository `%s`' % repo_name
1060 #self._compare_error(id_, expected, given=response.body)
1060 #self._compare_error(id_, expected, given=response.body)
1061
1061
1062 fixture.destroy_repo_group(repo_group_name)
1062 fixture.destroy_repo_group(repo_group_name)
1063
1063
1064 def test_api_create_repo_unknown_owner(self):
1064 def test_api_create_repo_unknown_owner(self):
1065 repo_name = u'api-repo'
1065 repo_name = u'api-repo'
1066 owner = 'i-dont-exist'
1066 owner = 'i-dont-exist'
1067 id_, params = _build_data(self.apikey, 'create_repo',
1067 id_, params = _build_data(self.apikey, 'create_repo',
1068 repo_name=repo_name,
1068 repo_name=repo_name,
1069 owner=owner,
1069 owner=owner,
1070 repo_type=self.REPO_TYPE,
1070 repo_type=self.REPO_TYPE,
1071 )
1071 )
1072 response = api_call(self, params)
1072 response = api_call(self, params)
1073 expected = 'user `%s` does not exist' % owner
1073 expected = 'user `%s` does not exist' % owner
1074 self._compare_error(id_, expected, given=response.body)
1074 self._compare_error(id_, expected, given=response.body)
1075
1075
1076 def test_api_create_repo_dont_specify_owner(self):
1076 def test_api_create_repo_dont_specify_owner(self):
1077 repo_name = u'api-repo'
1077 repo_name = u'api-repo'
1078 owner = 'i-dont-exist'
1078 owner = 'i-dont-exist'
1079 id_, params = _build_data(self.apikey, 'create_repo',
1079 id_, params = _build_data(self.apikey, 'create_repo',
1080 repo_name=repo_name,
1080 repo_name=repo_name,
1081 repo_type=self.REPO_TYPE,
1081 repo_type=self.REPO_TYPE,
1082 )
1082 )
1083 response = api_call(self, params)
1083 response = api_call(self, params)
1084
1084
1085 repo = RepoModel().get_by_repo_name(repo_name)
1085 repo = RepoModel().get_by_repo_name(repo_name)
1086 assert repo != None
1086 assert repo != None
1087 ret = {
1087 ret = {
1088 'msg': 'Created new repository `%s`' % repo_name,
1088 'msg': 'Created new repository `%s`' % repo_name,
1089 'success': True,
1089 'success': True,
1090 'task': None,
1090 'task': None,
1091 }
1091 }
1092 expected = ret
1092 expected = ret
1093 self._compare_ok(id_, expected, given=response.body)
1093 self._compare_ok(id_, expected, given=response.body)
1094 fixture.destroy_repo(repo_name)
1094 fixture.destroy_repo(repo_name)
1095
1095
1096 def test_api_create_repo_by_non_admin(self):
1096 def test_api_create_repo_by_non_admin(self):
1097 repo_name = u'api-repo'
1097 repo_name = u'api-repo'
1098 owner = 'i-dont-exist'
1098 owner = 'i-dont-exist'
1099 id_, params = _build_data(self.apikey_regular, 'create_repo',
1099 id_, params = _build_data(self.apikey_regular, 'create_repo',
1100 repo_name=repo_name,
1100 repo_name=repo_name,
1101 repo_type=self.REPO_TYPE,
1101 repo_type=self.REPO_TYPE,
1102 )
1102 )
1103 response = api_call(self, params)
1103 response = api_call(self, params)
1104
1104
1105 repo = RepoModel().get_by_repo_name(repo_name)
1105 repo = RepoModel().get_by_repo_name(repo_name)
1106 assert repo != None
1106 assert repo != None
1107 ret = {
1107 ret = {
1108 'msg': 'Created new repository `%s`' % repo_name,
1108 'msg': 'Created new repository `%s`' % repo_name,
1109 'success': True,
1109 'success': True,
1110 'task': None,
1110 'task': None,
1111 }
1111 }
1112 expected = ret
1112 expected = ret
1113 self._compare_ok(id_, expected, given=response.body)
1113 self._compare_ok(id_, expected, given=response.body)
1114 fixture.destroy_repo(repo_name)
1114 fixture.destroy_repo(repo_name)
1115
1115
1116 def test_api_create_repo_by_non_admin_specify_owner(self):
1116 def test_api_create_repo_by_non_admin_specify_owner(self):
1117 repo_name = u'api-repo'
1117 repo_name = u'api-repo'
1118 owner = 'i-dont-exist'
1118 owner = 'i-dont-exist'
1119 id_, params = _build_data(self.apikey_regular, 'create_repo',
1119 id_, params = _build_data(self.apikey_regular, 'create_repo',
1120 repo_name=repo_name,
1120 repo_name=repo_name,
1121 repo_type=self.REPO_TYPE,
1121 repo_type=self.REPO_TYPE,
1122 owner=owner)
1122 owner=owner)
1123 response = api_call(self, params)
1123 response = api_call(self, params)
1124
1124
1125 expected = 'Only Kallithea admin can specify `owner` param'
1125 expected = 'Only Kallithea admin can specify `owner` param'
1126 self._compare_error(id_, expected, given=response.body)
1126 self._compare_error(id_, expected, given=response.body)
1127 fixture.destroy_repo(repo_name)
1127 fixture.destroy_repo(repo_name)
1128
1128
1129 def test_api_create_repo_exists(self):
1129 def test_api_create_repo_exists(self):
1130 repo_name = self.REPO
1130 repo_name = self.REPO
1131 id_, params = _build_data(self.apikey, 'create_repo',
1131 id_, params = _build_data(self.apikey, 'create_repo',
1132 repo_name=repo_name,
1132 repo_name=repo_name,
1133 owner=TEST_USER_ADMIN_LOGIN,
1133 owner=TEST_USER_ADMIN_LOGIN,
1134 repo_type=self.REPO_TYPE,)
1134 repo_type=self.REPO_TYPE,)
1135 response = api_call(self, params)
1135 response = api_call(self, params)
1136 expected = "repo `%s` already exist" % repo_name
1136 expected = "repo `%s` already exist" % repo_name
1137 self._compare_error(id_, expected, given=response.body)
1137 self._compare_error(id_, expected, given=response.body)
1138
1138
1139 @mock.patch.object(RepoModel, 'create', crash)
1139 @mock.patch.object(RepoModel, 'create', crash)
1140 def test_api_create_repo_exception_occurred(self):
1140 def test_api_create_repo_exception_occurred(self):
1141 repo_name = u'api-repo'
1141 repo_name = u'api-repo'
1142 id_, params = _build_data(self.apikey, 'create_repo',
1142 id_, params = _build_data(self.apikey, 'create_repo',
1143 repo_name=repo_name,
1143 repo_name=repo_name,
1144 owner=TEST_USER_ADMIN_LOGIN,
1144 owner=TEST_USER_ADMIN_LOGIN,
1145 repo_type=self.REPO_TYPE,)
1145 repo_type=self.REPO_TYPE,)
1146 response = api_call(self, params)
1146 response = api_call(self, params)
1147 expected = 'failed to create repository `%s`' % repo_name
1147 expected = 'failed to create repository `%s`' % repo_name
1148 self._compare_error(id_, expected, given=response.body)
1148 self._compare_error(id_, expected, given=response.body)
1149
1149
1150 @parametrize('changing_attr,updates', [
1150 @parametrize('changing_attr,updates', [
1151 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1151 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1152 ('description', {'description': u'new description'}),
1152 ('description', {'description': u'new description'}),
1153 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1153 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1154 ('clone_uri', {'clone_uri': None}),
1154 ('clone_uri', {'clone_uri': None}),
1155 ('landing_rev', {'landing_rev': 'branch:master'}),
1155 ('landing_rev', {'landing_rev': 'branch:master'}),
1156 ('enable_statistics', {'enable_statistics': True}),
1156 ('enable_statistics', {'enable_statistics': True}),
1157 ('enable_locking', {'enable_locking': True}),
1157 ('enable_locking', {'enable_locking': True}),
1158 ('enable_downloads', {'enable_downloads': True}),
1158 ('enable_downloads', {'enable_downloads': True}),
1159 ('name', {'name': u'new_repo_name'}),
1159 ('name', {'name': u'new_repo_name'}),
1160 ('repo_group', {'group': u'test_group_for_update'}),
1160 ('repo_group', {'group': u'test_group_for_update'}),
1161 ])
1161 ])
1162 def test_api_update_repo(self, changing_attr, updates):
1162 def test_api_update_repo(self, changing_attr, updates):
1163 repo_name = u'api_update_me'
1163 repo_name = u'api_update_me'
1164 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1164 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1165 if changing_attr == 'repo_group':
1165 if changing_attr == 'repo_group':
1166 fixture.create_repo_group(updates['group'])
1166 fixture.create_repo_group(updates['group'])
1167
1167
1168 id_, params = _build_data(self.apikey, 'update_repo',
1168 id_, params = _build_data(self.apikey, 'update_repo',
1169 repoid=repo_name, **updates)
1169 repoid=repo_name, **updates)
1170 response = api_call(self, params)
1170 response = api_call(self, params)
1171 if changing_attr == 'name':
1171 if changing_attr == 'name':
1172 repo_name = updates['name']
1172 repo_name = updates['name']
1173 if changing_attr == 'repo_group':
1173 if changing_attr == 'repo_group':
1174 repo_name = u'/'.join([updates['group'], repo_name])
1174 repo_name = u'/'.join([updates['group'], repo_name])
1175 try:
1175 try:
1176 expected = {
1176 expected = {
1177 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1177 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1178 'repository': repo.get_api_data()
1178 'repository': repo.get_api_data()
1179 }
1179 }
1180 self._compare_ok(id_, expected, given=response.body)
1180 self._compare_ok(id_, expected, given=response.body)
1181 finally:
1181 finally:
1182 fixture.destroy_repo(repo_name)
1182 fixture.destroy_repo(repo_name)
1183 if changing_attr == 'repo_group':
1183 if changing_attr == 'repo_group':
1184 fixture.destroy_repo_group(updates['group'])
1184 fixture.destroy_repo_group(updates['group'])
1185
1185
1186 @parametrize('changing_attr,updates', [
1186 @parametrize('changing_attr,updates', [
1187 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1187 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1188 ('description', {'description': u'new description'}),
1188 ('description', {'description': u'new description'}),
1189 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1189 ('clone_uri', {'clone_uri': 'http://example.com/repo'}),
1190 ('clone_uri', {'clone_uri': None}),
1190 ('clone_uri', {'clone_uri': None}),
1191 ('landing_rev', {'landing_rev': 'branch:master'}),
1191 ('landing_rev', {'landing_rev': 'branch:master'}),
1192 ('enable_statistics', {'enable_statistics': True}),
1192 ('enable_statistics', {'enable_statistics': True}),
1193 ('enable_locking', {'enable_locking': True}),
1193 ('enable_locking', {'enable_locking': True}),
1194 ('enable_downloads', {'enable_downloads': True}),
1194 ('enable_downloads', {'enable_downloads': True}),
1195 ('name', {'name': u'new_repo_name'}),
1195 ('name', {'name': u'new_repo_name'}),
1196 ('repo_group', {'group': u'test_group_for_update'}),
1196 ('repo_group', {'group': u'test_group_for_update'}),
1197 ])
1197 ])
1198 def test_api_update_group_repo(self, changing_attr, updates):
1198 def test_api_update_group_repo(self, changing_attr, updates):
1199 group_name = u'lololo'
1199 group_name = u'lololo'
1200 fixture.create_repo_group(group_name)
1200 fixture.create_repo_group(group_name)
1201 repo_name = u'%s/api_update_me' % group_name
1201 repo_name = u'%s/api_update_me' % group_name
1202 repo = fixture.create_repo(repo_name, repo_group=group_name, repo_type=self.REPO_TYPE)
1202 repo = fixture.create_repo(repo_name, repo_group=group_name, repo_type=self.REPO_TYPE)
1203 if changing_attr == 'repo_group':
1203 if changing_attr == 'repo_group':
1204 fixture.create_repo_group(updates['group'])
1204 fixture.create_repo_group(updates['group'])
1205
1205
1206 id_, params = _build_data(self.apikey, 'update_repo',
1206 id_, params = _build_data(self.apikey, 'update_repo',
1207 repoid=repo_name, **updates)
1207 repoid=repo_name, **updates)
1208 response = api_call(self, params)
1208 response = api_call(self, params)
1209 if changing_attr == 'name':
1209 if changing_attr == 'name':
1210 repo_name = u'%s/%s' % (group_name, updates['name'])
1210 repo_name = u'%s/%s' % (group_name, updates['name'])
1211 if changing_attr == 'repo_group':
1211 if changing_attr == 'repo_group':
1212 repo_name = u'/'.join([updates['group'], repo_name.rsplit('/', 1)[-1]])
1212 repo_name = u'/'.join([updates['group'], repo_name.rsplit('/', 1)[-1]])
1213 try:
1213 try:
1214 expected = {
1214 expected = {
1215 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1215 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
1216 'repository': repo.get_api_data()
1216 'repository': repo.get_api_data()
1217 }
1217 }
1218 self._compare_ok(id_, expected, given=response.body)
1218 self._compare_ok(id_, expected, given=response.body)
1219 finally:
1219 finally:
1220 fixture.destroy_repo(repo_name)
1220 fixture.destroy_repo(repo_name)
1221 if changing_attr == 'repo_group':
1221 if changing_attr == 'repo_group':
1222 fixture.destroy_repo_group(updates['group'])
1222 fixture.destroy_repo_group(updates['group'])
1223 fixture.destroy_repo_group(group_name)
1223 fixture.destroy_repo_group(group_name)
1224
1224
1225 def test_api_update_repo_repo_group_does_not_exist(self):
1225 def test_api_update_repo_repo_group_does_not_exist(self):
1226 repo_name = u'admin_owned'
1226 repo_name = u'admin_owned'
1227 fixture.create_repo(repo_name)
1227 fixture.create_repo(repo_name)
1228 updates = {'group': 'test_group_for_update'}
1228 updates = {'group': 'test_group_for_update'}
1229 id_, params = _build_data(self.apikey, 'update_repo',
1229 id_, params = _build_data(self.apikey, 'update_repo',
1230 repoid=repo_name, **updates)
1230 repoid=repo_name, **updates)
1231 response = api_call(self, params)
1231 response = api_call(self, params)
1232 try:
1232 try:
1233 expected = 'repository group `%s` does not exist' % updates['group']
1233 expected = 'repository group `%s` does not exist' % updates['group']
1234 self._compare_error(id_, expected, given=response.body)
1234 self._compare_error(id_, expected, given=response.body)
1235 finally:
1235 finally:
1236 fixture.destroy_repo(repo_name)
1236 fixture.destroy_repo(repo_name)
1237
1237
1238 def test_api_update_repo_regular_user_not_allowed(self):
1238 def test_api_update_repo_regular_user_not_allowed(self):
1239 repo_name = u'admin_owned'
1239 repo_name = u'admin_owned'
1240 fixture.create_repo(repo_name)
1240 fixture.create_repo(repo_name)
1241 updates = {'description': 'something else'}
1241 updates = {'description': 'something else'}
1242 id_, params = _build_data(self.apikey_regular, 'update_repo',
1242 id_, params = _build_data(self.apikey_regular, 'update_repo',
1243 repoid=repo_name, **updates)
1243 repoid=repo_name, **updates)
1244 response = api_call(self, params)
1244 response = api_call(self, params)
1245 try:
1245 try:
1246 expected = 'repository `%s` does not exist' % repo_name
1246 expected = 'repository `%s` does not exist' % repo_name
1247 self._compare_error(id_, expected, given=response.body)
1247 self._compare_error(id_, expected, given=response.body)
1248 finally:
1248 finally:
1249 fixture.destroy_repo(repo_name)
1249 fixture.destroy_repo(repo_name)
1250
1250
1251 @mock.patch.object(RepoModel, 'update', crash)
1251 @mock.patch.object(RepoModel, 'update', crash)
1252 def test_api_update_repo_exception_occurred(self):
1252 def test_api_update_repo_exception_occurred(self):
1253 repo_name = u'api_update_me'
1253 repo_name = u'api_update_me'
1254 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1254 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1255 id_, params = _build_data(self.apikey, 'update_repo',
1255 id_, params = _build_data(self.apikey, 'update_repo',
1256 repoid=repo_name, owner=TEST_USER_ADMIN_LOGIN,)
1256 repoid=repo_name, owner=TEST_USER_ADMIN_LOGIN,)
1257 response = api_call(self, params)
1257 response = api_call(self, params)
1258 try:
1258 try:
1259 expected = 'failed to update repo `%s`' % repo_name
1259 expected = 'failed to update repo `%s`' % repo_name
1260 self._compare_error(id_, expected, given=response.body)
1260 self._compare_error(id_, expected, given=response.body)
1261 finally:
1261 finally:
1262 fixture.destroy_repo(repo_name)
1262 fixture.destroy_repo(repo_name)
1263
1263
1264 def test_api_update_repo_regular_user_change_repo_name(self):
1264 def test_api_update_repo_regular_user_change_repo_name(self):
1265 repo_name = u'admin_owned'
1265 repo_name = u'admin_owned'
1266 new_repo_name = u'new_repo_name'
1266 new_repo_name = u'new_repo_name'
1267 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1267 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1268 RepoModel().grant_user_permission(repo=repo_name,
1268 RepoModel().grant_user_permission(repo=repo_name,
1269 user=self.TEST_USER_LOGIN,
1269 user=self.TEST_USER_LOGIN,
1270 perm='repository.admin')
1270 perm='repository.admin')
1271 UserModel().revoke_perm('default', 'hg.create.repository')
1271 UserModel().revoke_perm('default', 'hg.create.repository')
1272 UserModel().grant_perm('default', 'hg.create.none')
1272 UserModel().grant_perm('default', 'hg.create.none')
1273 updates = {'name': new_repo_name}
1273 updates = {'name': new_repo_name}
1274 id_, params = _build_data(self.apikey_regular, 'update_repo',
1274 id_, params = _build_data(self.apikey_regular, 'update_repo',
1275 repoid=repo_name, **updates)
1275 repoid=repo_name, **updates)
1276 response = api_call(self, params)
1276 response = api_call(self, params)
1277 try:
1277 try:
1278 expected = 'no permission to create (or move) repositories'
1278 expected = 'no permission to create (or move) repositories'
1279 self._compare_error(id_, expected, given=response.body)
1279 self._compare_error(id_, expected, given=response.body)
1280 finally:
1280 finally:
1281 fixture.destroy_repo(repo_name)
1281 fixture.destroy_repo(repo_name)
1282 fixture.destroy_repo(new_repo_name)
1282 fixture.destroy_repo(new_repo_name)
1283
1283
1284 def test_api_update_repo_regular_user_change_repo_name_allowed(self):
1284 def test_api_update_repo_regular_user_change_repo_name_allowed(self):
1285 repo_name = u'admin_owned'
1285 repo_name = u'admin_owned'
1286 new_repo_name = u'new_repo_name'
1286 new_repo_name = u'new_repo_name'
1287 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1287 repo = fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1288 RepoModel().grant_user_permission(repo=repo_name,
1288 RepoModel().grant_user_permission(repo=repo_name,
1289 user=self.TEST_USER_LOGIN,
1289 user=self.TEST_USER_LOGIN,
1290 perm='repository.admin')
1290 perm='repository.admin')
1291 UserModel().revoke_perm('default', 'hg.create.none')
1291 UserModel().revoke_perm('default', 'hg.create.none')
1292 UserModel().grant_perm('default', 'hg.create.repository')
1292 UserModel().grant_perm('default', 'hg.create.repository')
1293 updates = {'name': new_repo_name}
1293 updates = {'name': new_repo_name}
1294 id_, params = _build_data(self.apikey_regular, 'update_repo',
1294 id_, params = _build_data(self.apikey_regular, 'update_repo',
1295 repoid=repo_name, **updates)
1295 repoid=repo_name, **updates)
1296 response = api_call(self, params)
1296 response = api_call(self, params)
1297 try:
1297 try:
1298 expected = {
1298 expected = {
1299 'msg': 'updated repo ID:%s %s' % (repo.repo_id, new_repo_name),
1299 'msg': 'updated repo ID:%s %s' % (repo.repo_id, new_repo_name),
1300 'repository': repo.get_api_data()
1300 'repository': repo.get_api_data()
1301 }
1301 }
1302 self._compare_ok(id_, expected, given=response.body)
1302 self._compare_ok(id_, expected, given=response.body)
1303 finally:
1303 finally:
1304 fixture.destroy_repo(repo_name)
1304 fixture.destroy_repo(repo_name)
1305 fixture.destroy_repo(new_repo_name)
1305 fixture.destroy_repo(new_repo_name)
1306
1306
1307 def test_api_update_repo_regular_user_change_owner(self):
1307 def test_api_update_repo_regular_user_change_owner(self):
1308 repo_name = u'admin_owned'
1308 repo_name = u'admin_owned'
1309 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1309 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1310 RepoModel().grant_user_permission(repo=repo_name,
1310 RepoModel().grant_user_permission(repo=repo_name,
1311 user=self.TEST_USER_LOGIN,
1311 user=self.TEST_USER_LOGIN,
1312 perm='repository.admin')
1312 perm='repository.admin')
1313 updates = {'owner': TEST_USER_ADMIN_LOGIN}
1313 updates = {'owner': TEST_USER_ADMIN_LOGIN}
1314 id_, params = _build_data(self.apikey_regular, 'update_repo',
1314 id_, params = _build_data(self.apikey_regular, 'update_repo',
1315 repoid=repo_name, **updates)
1315 repoid=repo_name, **updates)
1316 response = api_call(self, params)
1316 response = api_call(self, params)
1317 try:
1317 try:
1318 expected = 'Only Kallithea admin can specify `owner` param'
1318 expected = 'Only Kallithea admin can specify `owner` param'
1319 self._compare_error(id_, expected, given=response.body)
1319 self._compare_error(id_, expected, given=response.body)
1320 finally:
1320 finally:
1321 fixture.destroy_repo(repo_name)
1321 fixture.destroy_repo(repo_name)
1322
1322
1323 def test_api_delete_repo(self):
1323 def test_api_delete_repo(self):
1324 repo_name = u'api_delete_me'
1324 repo_name = u'api_delete_me'
1325 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1325 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1326
1326
1327 id_, params = _build_data(self.apikey, 'delete_repo',
1327 id_, params = _build_data(self.apikey, 'delete_repo',
1328 repoid=repo_name, )
1328 repoid=repo_name, )
1329 response = api_call(self, params)
1329 response = api_call(self, params)
1330
1330
1331 ret = {
1331 ret = {
1332 'msg': 'Deleted repository `%s`' % repo_name,
1332 'msg': 'Deleted repository `%s`' % repo_name,
1333 'success': True
1333 'success': True
1334 }
1334 }
1335 try:
1335 try:
1336 expected = ret
1336 expected = ret
1337 self._compare_ok(id_, expected, given=response.body)
1337 self._compare_ok(id_, expected, given=response.body)
1338 finally:
1338 finally:
1339 fixture.destroy_repo(repo_name)
1339 fixture.destroy_repo(repo_name)
1340
1340
1341 def test_api_delete_repo_by_non_admin(self):
1341 def test_api_delete_repo_by_non_admin(self):
1342 repo_name = u'api_delete_me'
1342 repo_name = u'api_delete_me'
1343 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
1343 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE,
1344 cur_user=self.TEST_USER_LOGIN)
1344 cur_user=self.TEST_USER_LOGIN)
1345 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1345 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1346 repoid=repo_name, )
1346 repoid=repo_name, )
1347 response = api_call(self, params)
1347 response = api_call(self, params)
1348
1348
1349 ret = {
1349 ret = {
1350 'msg': 'Deleted repository `%s`' % repo_name,
1350 'msg': 'Deleted repository `%s`' % repo_name,
1351 'success': True
1351 'success': True
1352 }
1352 }
1353 try:
1353 try:
1354 expected = ret
1354 expected = ret
1355 self._compare_ok(id_, expected, given=response.body)
1355 self._compare_ok(id_, expected, given=response.body)
1356 finally:
1356 finally:
1357 fixture.destroy_repo(repo_name)
1357 fixture.destroy_repo(repo_name)
1358
1358
1359 def test_api_delete_repo_by_non_admin_no_permission(self):
1359 def test_api_delete_repo_by_non_admin_no_permission(self):
1360 repo_name = u'api_delete_me'
1360 repo_name = u'api_delete_me'
1361 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1361 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1362 try:
1362 try:
1363 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1363 id_, params = _build_data(self.apikey_regular, 'delete_repo',
1364 repoid=repo_name, )
1364 repoid=repo_name, )
1365 response = api_call(self, params)
1365 response = api_call(self, params)
1366 expected = 'repository `%s` does not exist' % (repo_name)
1366 expected = 'repository `%s` does not exist' % (repo_name)
1367 self._compare_error(id_, expected, given=response.body)
1367 self._compare_error(id_, expected, given=response.body)
1368 finally:
1368 finally:
1369 fixture.destroy_repo(repo_name)
1369 fixture.destroy_repo(repo_name)
1370
1370
1371 def test_api_delete_repo_exception_occurred(self):
1371 def test_api_delete_repo_exception_occurred(self):
1372 repo_name = u'api_delete_me'
1372 repo_name = u'api_delete_me'
1373 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1373 fixture.create_repo(repo_name, repo_type=self.REPO_TYPE)
1374 try:
1374 try:
1375 with mock.patch.object(RepoModel, 'delete', crash):
1375 with mock.patch.object(RepoModel, 'delete', crash):
1376 id_, params = _build_data(self.apikey, 'delete_repo',
1376 id_, params = _build_data(self.apikey, 'delete_repo',
1377 repoid=repo_name, )
1377 repoid=repo_name, )
1378 response = api_call(self, params)
1378 response = api_call(self, params)
1379
1379
1380 expected = 'failed to delete repository `%s`' % repo_name
1380 expected = 'failed to delete repository `%s`' % repo_name
1381 self._compare_error(id_, expected, given=response.body)
1381 self._compare_error(id_, expected, given=response.body)
1382 finally:
1382 finally:
1383 fixture.destroy_repo(repo_name)
1383 fixture.destroy_repo(repo_name)
1384
1384
1385 def test_api_fork_repo(self):
1385 def test_api_fork_repo(self):
1386 fork_name = u'api-repo-fork'
1386 fork_name = u'api-repo-fork'
1387 id_, params = _build_data(self.apikey, 'fork_repo',
1387 id_, params = _build_data(self.apikey, 'fork_repo',
1388 repoid=self.REPO,
1388 repoid=self.REPO,
1389 fork_name=fork_name,
1389 fork_name=fork_name,
1390 owner=TEST_USER_ADMIN_LOGIN,
1390 owner=TEST_USER_ADMIN_LOGIN,
1391 )
1391 )
1392 response = api_call(self, params)
1392 response = api_call(self, params)
1393
1393
1394 ret = {
1394 ret = {
1395 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1395 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1396 fork_name),
1396 fork_name),
1397 'success': True,
1397 'success': True,
1398 'task': None,
1398 'task': None,
1399 }
1399 }
1400 expected = ret
1400 expected = ret
1401 self._compare_ok(id_, expected, given=response.body)
1401 self._compare_ok(id_, expected, given=response.body)
1402 fixture.destroy_repo(fork_name)
1402 fixture.destroy_repo(fork_name)
1403
1403
1404 @parametrize('fork_name', [
1404 @parametrize('fork_name', [
1405 u'api-repo-fork',
1405 u'api-repo-fork',
1406 u'%s/api-repo-fork' % TEST_REPO_GROUP,
1406 u'%s/api-repo-fork' % TEST_REPO_GROUP,
1407 ])
1407 ])
1408 def test_api_fork_repo_non_admin(self, fork_name):
1408 def test_api_fork_repo_non_admin(self, fork_name):
1409 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1409 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1410 repoid=self.REPO,
1410 repoid=self.REPO,
1411 fork_name=fork_name,
1411 fork_name=fork_name,
1412 )
1412 )
1413 response = api_call(self, params)
1413 response = api_call(self, params)
1414
1414
1415 ret = {
1415 ret = {
1416 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1416 'msg': 'Created fork of `%s` as `%s`' % (self.REPO,
1417 fork_name),
1417 fork_name),
1418 'success': True,
1418 'success': True,
1419 'task': None,
1419 'task': None,
1420 }
1420 }
1421 expected = ret
1421 expected = ret
1422 self._compare_ok(id_, expected, given=response.body)
1422 self._compare_ok(id_, expected, given=response.body)
1423 fixture.destroy_repo(fork_name)
1423 fixture.destroy_repo(fork_name)
1424
1424
1425 def test_api_fork_repo_non_admin_specify_owner(self):
1425 def test_api_fork_repo_non_admin_specify_owner(self):
1426 fork_name = u'api-repo-fork'
1426 fork_name = u'api-repo-fork'
1427 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1427 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1428 repoid=self.REPO,
1428 repoid=self.REPO,
1429 fork_name=fork_name,
1429 fork_name=fork_name,
1430 owner=TEST_USER_ADMIN_LOGIN,
1430 owner=TEST_USER_ADMIN_LOGIN,
1431 )
1431 )
1432 response = api_call(self, params)
1432 response = api_call(self, params)
1433 expected = 'Only Kallithea admin can specify `owner` param'
1433 expected = 'Only Kallithea admin can specify `owner` param'
1434 self._compare_error(id_, expected, given=response.body)
1434 self._compare_error(id_, expected, given=response.body)
1435 fixture.destroy_repo(fork_name)
1435 fixture.destroy_repo(fork_name)
1436
1436
1437 def test_api_fork_repo_non_admin_no_permission_to_fork(self):
1437 def test_api_fork_repo_non_admin_no_permission_to_fork(self):
1438 RepoModel().grant_user_permission(repo=self.REPO,
1438 RepoModel().grant_user_permission(repo=self.REPO,
1439 user=self.TEST_USER_LOGIN,
1439 user=self.TEST_USER_LOGIN,
1440 perm='repository.none')
1440 perm='repository.none')
1441 fork_name = u'api-repo-fork'
1441 fork_name = u'api-repo-fork'
1442 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1442 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1443 repoid=self.REPO,
1443 repoid=self.REPO,
1444 fork_name=fork_name,
1444 fork_name=fork_name,
1445 )
1445 )
1446 response = api_call(self, params)
1446 response = api_call(self, params)
1447 expected = 'repository `%s` does not exist' % (self.REPO)
1447 expected = 'repository `%s` does not exist' % (self.REPO)
1448 self._compare_error(id_, expected, given=response.body)
1448 self._compare_error(id_, expected, given=response.body)
1449 fixture.destroy_repo(fork_name)
1449 fixture.destroy_repo(fork_name)
1450
1450
1451 @parametrize('name,perm', [
1451 @parametrize('name,perm', [
1452 ('read', 'repository.read'),
1452 ('read', 'repository.read'),
1453 ('write', 'repository.write'),
1453 ('write', 'repository.write'),
1454 ('admin', 'repository.admin'),
1454 ('admin', 'repository.admin'),
1455 ])
1455 ])
1456 def test_api_fork_repo_non_admin_no_create_repo_permission(self, name, perm):
1456 def test_api_fork_repo_non_admin_no_create_repo_permission(self, name, perm):
1457 fork_name = u'api-repo-fork'
1457 fork_name = u'api-repo-fork'
1458 # regardless of base repository permission, forking is disallowed
1458 # regardless of base repository permission, forking is disallowed
1459 # when repository creation is disabled
1459 # when repository creation is disabled
1460 RepoModel().grant_user_permission(repo=self.REPO,
1460 RepoModel().grant_user_permission(repo=self.REPO,
1461 user=self.TEST_USER_LOGIN,
1461 user=self.TEST_USER_LOGIN,
1462 perm=perm)
1462 perm=perm)
1463 UserModel().revoke_perm('default', 'hg.create.repository')
1463 UserModel().revoke_perm('default', 'hg.create.repository')
1464 UserModel().grant_perm('default', 'hg.create.none')
1464 UserModel().grant_perm('default', 'hg.create.none')
1465 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1465 id_, params = _build_data(self.apikey_regular, 'fork_repo',
1466 repoid=self.REPO,
1466 repoid=self.REPO,
1467 fork_name=fork_name,
1467 fork_name=fork_name,
1468 )
1468 )
1469 response = api_call(self, params)
1469 response = api_call(self, params)
1470 expected = 'no permission to create repositories'
1470 expected = 'no permission to create repositories'
1471 self._compare_error(id_, expected, given=response.body)
1471 self._compare_error(id_, expected, given=response.body)
1472 fixture.destroy_repo(fork_name)
1472 fixture.destroy_repo(fork_name)
1473
1473
1474 def test_api_fork_repo_unknown_owner(self):
1474 def test_api_fork_repo_unknown_owner(self):
1475 fork_name = u'api-repo-fork'
1475 fork_name = u'api-repo-fork'
1476 owner = 'i-dont-exist'
1476 owner = 'i-dont-exist'
1477 id_, params = _build_data(self.apikey, 'fork_repo',
1477 id_, params = _build_data(self.apikey, 'fork_repo',
1478 repoid=self.REPO,
1478 repoid=self.REPO,
1479 fork_name=fork_name,
1479 fork_name=fork_name,
1480 owner=owner,
1480 owner=owner,
1481 )
1481 )
1482 response = api_call(self, params)
1482 response = api_call(self, params)
1483 expected = 'user `%s` does not exist' % owner
1483 expected = 'user `%s` does not exist' % owner
1484 self._compare_error(id_, expected, given=response.body)
1484 self._compare_error(id_, expected, given=response.body)
1485
1485
1486 def test_api_fork_repo_fork_exists(self):
1486 def test_api_fork_repo_fork_exists(self):
1487 fork_name = u'api-repo-fork'
1487 fork_name = u'api-repo-fork'
1488 fixture.create_fork(self.REPO, fork_name)
1488 fixture.create_fork(self.REPO, fork_name)
1489
1489
1490 try:
1490 try:
1491 fork_name = u'api-repo-fork'
1491 fork_name = u'api-repo-fork'
1492
1492
1493 id_, params = _build_data(self.apikey, 'fork_repo',
1493 id_, params = _build_data(self.apikey, 'fork_repo',
1494 repoid=self.REPO,
1494 repoid=self.REPO,
1495 fork_name=fork_name,
1495 fork_name=fork_name,
1496 owner=TEST_USER_ADMIN_LOGIN,
1496 owner=TEST_USER_ADMIN_LOGIN,
1497 )
1497 )
1498 response = api_call(self, params)
1498 response = api_call(self, params)
1499
1499
1500 expected = "fork `%s` already exist" % fork_name
1500 expected = "fork `%s` already exist" % fork_name
1501 self._compare_error(id_, expected, given=response.body)
1501 self._compare_error(id_, expected, given=response.body)
1502 finally:
1502 finally:
1503 fixture.destroy_repo(fork_name)
1503 fixture.destroy_repo(fork_name)
1504
1504
1505 def test_api_fork_repo_repo_exists(self):
1505 def test_api_fork_repo_repo_exists(self):
1506 fork_name = self.REPO
1506 fork_name = self.REPO
1507
1507
1508 id_, params = _build_data(self.apikey, 'fork_repo',
1508 id_, params = _build_data(self.apikey, 'fork_repo',
1509 repoid=self.REPO,
1509 repoid=self.REPO,
1510 fork_name=fork_name,
1510 fork_name=fork_name,
1511 owner=TEST_USER_ADMIN_LOGIN,
1511 owner=TEST_USER_ADMIN_LOGIN,
1512 )
1512 )
1513 response = api_call(self, params)
1513 response = api_call(self, params)
1514
1514
1515 expected = "repo `%s` already exist" % fork_name
1515 expected = "repo `%s` already exist" % fork_name
1516 self._compare_error(id_, expected, given=response.body)
1516 self._compare_error(id_, expected, given=response.body)
1517
1517
1518 @mock.patch.object(RepoModel, 'create_fork', crash)
1518 @mock.patch.object(RepoModel, 'create_fork', crash)
1519 def test_api_fork_repo_exception_occurred(self):
1519 def test_api_fork_repo_exception_occurred(self):
1520 fork_name = u'api-repo-fork'
1520 fork_name = u'api-repo-fork'
1521 id_, params = _build_data(self.apikey, 'fork_repo',
1521 id_, params = _build_data(self.apikey, 'fork_repo',
1522 repoid=self.REPO,
1522 repoid=self.REPO,
1523 fork_name=fork_name,
1523 fork_name=fork_name,
1524 owner=TEST_USER_ADMIN_LOGIN,
1524 owner=TEST_USER_ADMIN_LOGIN,
1525 )
1525 )
1526 response = api_call(self, params)
1526 response = api_call(self, params)
1527
1527
1528 expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
1528 expected = 'failed to fork repository `%s` as `%s`' % (self.REPO,
1529 fork_name)
1529 fork_name)
1530 self._compare_error(id_, expected, given=response.body)
1530 self._compare_error(id_, expected, given=response.body)
1531
1531
1532 def test_api_get_user_group(self):
1532 def test_api_get_user_group(self):
1533 id_, params = _build_data(self.apikey, 'get_user_group',
1533 id_, params = _build_data(self.apikey, 'get_user_group',
1534 usergroupid=TEST_USER_GROUP)
1534 usergroupid=TEST_USER_GROUP)
1535 response = api_call(self, params)
1535 response = api_call(self, params)
1536
1536
1537 user_group = UserGroupModel().get_group(TEST_USER_GROUP)
1537 user_group = UserGroupModel().get_group(TEST_USER_GROUP)
1538 members = []
1538 members = []
1539 for user in user_group.members:
1539 for user in user_group.members:
1540 user = user.user
1540 user = user.user
1541 members.append(user.get_api_data())
1541 members.append(user.get_api_data())
1542
1542
1543 ret = user_group.get_api_data()
1543 ret = user_group.get_api_data()
1544 ret['members'] = members
1544 ret['members'] = members
1545 expected = ret
1545 expected = ret
1546 self._compare_ok(id_, expected, given=response.body)
1546 self._compare_ok(id_, expected, given=response.body)
1547
1547
1548 def test_api_get_user_groups(self):
1548 def test_api_get_user_groups(self):
1549 gr_name = u'test_user_group2'
1549 gr_name = u'test_user_group2'
1550 make_user_group(gr_name)
1550 make_user_group(gr_name)
1551
1551
1552 try:
1552 try:
1553 id_, params = _build_data(self.apikey, 'get_user_groups', )
1553 id_, params = _build_data(self.apikey, 'get_user_groups', )
1554 response = api_call(self, params)
1554 response = api_call(self, params)
1555
1555
1556 expected = []
1556 expected = []
1557 for gr_name in [TEST_USER_GROUP, u'test_user_group2']:
1557 for gr_name in [TEST_USER_GROUP, u'test_user_group2']:
1558 user_group = UserGroupModel().get_group(gr_name)
1558 user_group = UserGroupModel().get_group(gr_name)
1559 ret = user_group.get_api_data()
1559 ret = user_group.get_api_data()
1560 expected.append(ret)
1560 expected.append(ret)
1561 self._compare_ok(id_, expected, given=response.body)
1561 self._compare_ok(id_, expected, given=response.body)
1562 finally:
1562 finally:
1563 fixture.destroy_user_group(gr_name)
1563 fixture.destroy_user_group(gr_name)
1564
1564
1565 def test_api_create_user_group(self):
1565 def test_api_create_user_group(self):
1566 group_name = u'some_new_group'
1566 group_name = u'some_new_group'
1567 id_, params = _build_data(self.apikey, 'create_user_group',
1567 id_, params = _build_data(self.apikey, 'create_user_group',
1568 group_name=group_name)
1568 group_name=group_name)
1569 response = api_call(self, params)
1569 response = api_call(self, params)
1570
1570
1571 ret = {
1571 ret = {
1572 'msg': 'created new user group `%s`' % group_name,
1572 'msg': 'created new user group `%s`' % group_name,
1573 'user_group': jsonify(UserGroupModel() \
1573 'user_group': jsonify(UserGroupModel() \
1574 .get_by_name(group_name) \
1574 .get_by_name(group_name) \
1575 .get_api_data())
1575 .get_api_data())
1576 }
1576 }
1577 expected = ret
1577 expected = ret
1578 self._compare_ok(id_, expected, given=response.body)
1578 self._compare_ok(id_, expected, given=response.body)
1579
1579
1580 fixture.destroy_user_group(group_name)
1580 fixture.destroy_user_group(group_name)
1581
1581
1582 def test_api_get_user_group_that_exist(self):
1582 def test_api_get_user_group_that_exist(self):
1583 id_, params = _build_data(self.apikey, 'create_user_group',
1583 id_, params = _build_data(self.apikey, 'create_user_group',
1584 group_name=TEST_USER_GROUP)
1584 group_name=TEST_USER_GROUP)
1585 response = api_call(self, params)
1585 response = api_call(self, params)
1586
1586
1587 expected = "user group `%s` already exist" % TEST_USER_GROUP
1587 expected = "user group `%s` already exist" % TEST_USER_GROUP
1588 self._compare_error(id_, expected, given=response.body)
1588 self._compare_error(id_, expected, given=response.body)
1589
1589
1590 @mock.patch.object(UserGroupModel, 'create', crash)
1590 @mock.patch.object(UserGroupModel, 'create', crash)
1591 def test_api_get_user_group_exception_occurred(self):
1591 def test_api_get_user_group_exception_occurred(self):
1592 group_name = u'exception_happens'
1592 group_name = u'exception_happens'
1593 id_, params = _build_data(self.apikey, 'create_user_group',
1593 id_, params = _build_data(self.apikey, 'create_user_group',
1594 group_name=group_name)
1594 group_name=group_name)
1595 response = api_call(self, params)
1595 response = api_call(self, params)
1596
1596
1597 expected = 'failed to create group `%s`' % group_name
1597 expected = 'failed to create group `%s`' % group_name
1598 self._compare_error(id_, expected, given=response.body)
1598 self._compare_error(id_, expected, given=response.body)
1599
1599
1600 @parametrize('changing_attr,updates', [
1600 @parametrize('changing_attr,updates', [
1601 ('group_name', {'group_name': u'new_group_name'}),
1601 ('group_name', {'group_name': u'new_group_name'}),
1602 ('group_name', {'group_name': u'test_group_for_update'}),
1602 ('group_name', {'group_name': u'test_group_for_update'}),
1603 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1603 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
1604 ('active', {'active': False}),
1604 ('active', {'active': False}),
1605 ('active', {'active': True}),
1605 ('active', {'active': True}),
1606 ])
1606 ])
1607 def test_api_update_user_group(self, changing_attr, updates):
1607 def test_api_update_user_group(self, changing_attr, updates):
1608 gr_name = u'test_group_for_update'
1608 gr_name = u'test_group_for_update'
1609 user_group = fixture.create_user_group(gr_name)
1609 user_group = fixture.create_user_group(gr_name)
1610 try:
1610 try:
1611 id_, params = _build_data(self.apikey, 'update_user_group',
1611 id_, params = _build_data(self.apikey, 'update_user_group',
1612 usergroupid=gr_name, **updates)
1612 usergroupid=gr_name, **updates)
1613 response = api_call(self, params)
1613 response = api_call(self, params)
1614 expected = {
1614 expected = {
1615 'msg': 'updated user group ID:%s %s' % (user_group.users_group_id,
1615 'msg': 'updated user group ID:%s %s' % (user_group.users_group_id,
1616 user_group.users_group_name),
1616 user_group.users_group_name),
1617 'user_group': user_group.get_api_data()
1617 'user_group': user_group.get_api_data()
1618 }
1618 }
1619 self._compare_ok(id_, expected, given=response.body)
1619 self._compare_ok(id_, expected, given=response.body)
1620 finally:
1620 finally:
1621 if changing_attr == 'group_name':
1621 if changing_attr == 'group_name':
1622 # switch to updated name for proper cleanup
1622 # switch to updated name for proper cleanup
1623 gr_name = updates['group_name']
1623 gr_name = updates['group_name']
1624 fixture.destroy_user_group(gr_name)
1624 fixture.destroy_user_group(gr_name)
1625
1625
1626 @mock.patch.object(UserGroupModel, 'update', crash)
1626 @mock.patch.object(UserGroupModel, 'update', crash)
1627 def test_api_update_user_group_exception_occurred(self):
1627 def test_api_update_user_group_exception_occurred(self):
1628 gr_name = u'test_group'
1628 gr_name = u'test_group'
1629 fixture.create_user_group(gr_name)
1629 fixture.create_user_group(gr_name)
1630 try:
1630 try:
1631 id_, params = _build_data(self.apikey, 'update_user_group',
1631 id_, params = _build_data(self.apikey, 'update_user_group',
1632 usergroupid=gr_name)
1632 usergroupid=gr_name)
1633 response = api_call(self, params)
1633 response = api_call(self, params)
1634 expected = 'failed to update user group `%s`' % gr_name
1634 expected = 'failed to update user group `%s`' % gr_name
1635 self._compare_error(id_, expected, given=response.body)
1635 self._compare_error(id_, expected, given=response.body)
1636 finally:
1636 finally:
1637 fixture.destroy_user_group(gr_name)
1637 fixture.destroy_user_group(gr_name)
1638
1638
1639 def test_api_add_user_to_user_group(self):
1639 def test_api_add_user_to_user_group(self):
1640 gr_name = u'test_group'
1640 gr_name = u'test_group'
1641 fixture.create_user_group(gr_name)
1641 fixture.create_user_group(gr_name)
1642 try:
1642 try:
1643 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1643 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1644 usergroupid=gr_name,
1644 usergroupid=gr_name,
1645 userid=TEST_USER_ADMIN_LOGIN)
1645 userid=TEST_USER_ADMIN_LOGIN)
1646 response = api_call(self, params)
1646 response = api_call(self, params)
1647 expected = {
1647 expected = {
1648 'msg': 'added member `%s` to user group `%s`' % (
1648 'msg': 'added member `%s` to user group `%s`' % (
1649 TEST_USER_ADMIN_LOGIN, gr_name),
1649 TEST_USER_ADMIN_LOGIN, gr_name),
1650 'success': True
1650 'success': True
1651 }
1651 }
1652 self._compare_ok(id_, expected, given=response.body)
1652 self._compare_ok(id_, expected, given=response.body)
1653 finally:
1653 finally:
1654 fixture.destroy_user_group(gr_name)
1654 fixture.destroy_user_group(gr_name)
1655
1655
1656 def test_api_add_user_to_user_group_that_doesnt_exist(self):
1656 def test_api_add_user_to_user_group_that_doesnt_exist(self):
1657 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1657 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1658 usergroupid='false-group',
1658 usergroupid='false-group',
1659 userid=TEST_USER_ADMIN_LOGIN)
1659 userid=TEST_USER_ADMIN_LOGIN)
1660 response = api_call(self, params)
1660 response = api_call(self, params)
1661
1661
1662 expected = 'user group `%s` does not exist' % 'false-group'
1662 expected = 'user group `%s` does not exist' % 'false-group'
1663 self._compare_error(id_, expected, given=response.body)
1663 self._compare_error(id_, expected, given=response.body)
1664
1664
1665 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
1665 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
1666 def test_api_add_user_to_user_group_exception_occurred(self):
1666 def test_api_add_user_to_user_group_exception_occurred(self):
1667 gr_name = u'test_group'
1667 gr_name = u'test_group'
1668 fixture.create_user_group(gr_name)
1668 fixture.create_user_group(gr_name)
1669 try:
1669 try:
1670 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1670 id_, params = _build_data(self.apikey, 'add_user_to_user_group',
1671 usergroupid=gr_name,
1671 usergroupid=gr_name,
1672 userid=TEST_USER_ADMIN_LOGIN)
1672 userid=TEST_USER_ADMIN_LOGIN)
1673 response = api_call(self, params)
1673 response = api_call(self, params)
1674 expected = 'failed to add member to user group `%s`' % gr_name
1674 expected = 'failed to add member to user group `%s`' % gr_name
1675 self._compare_error(id_, expected, given=response.body)
1675 self._compare_error(id_, expected, given=response.body)
1676 finally:
1676 finally:
1677 fixture.destroy_user_group(gr_name)
1677 fixture.destroy_user_group(gr_name)
1678
1678
1679 def test_api_remove_user_from_user_group(self):
1679 def test_api_remove_user_from_user_group(self):
1680 gr_name = u'test_group_3'
1680 gr_name = u'test_group_3'
1681 gr = fixture.create_user_group(gr_name)
1681 gr = fixture.create_user_group(gr_name)
1682 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1682 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1683 Session().commit()
1683 Session().commit()
1684 try:
1684 try:
1685 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1685 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1686 usergroupid=gr_name,
1686 usergroupid=gr_name,
1687 userid=TEST_USER_ADMIN_LOGIN)
1687 userid=TEST_USER_ADMIN_LOGIN)
1688 response = api_call(self, params)
1688 response = api_call(self, params)
1689 expected = {
1689 expected = {
1690 'msg': 'removed member `%s` from user group `%s`' % (
1690 'msg': 'removed member `%s` from user group `%s`' % (
1691 TEST_USER_ADMIN_LOGIN, gr_name
1691 TEST_USER_ADMIN_LOGIN, gr_name
1692 ),
1692 ),
1693 'success': True}
1693 'success': True}
1694 self._compare_ok(id_, expected, given=response.body)
1694 self._compare_ok(id_, expected, given=response.body)
1695 finally:
1695 finally:
1696 fixture.destroy_user_group(gr_name)
1696 fixture.destroy_user_group(gr_name)
1697
1697
1698 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
1698 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
1699 def test_api_remove_user_from_user_group_exception_occurred(self):
1699 def test_api_remove_user_from_user_group_exception_occurred(self):
1700 gr_name = u'test_group_3'
1700 gr_name = u'test_group_3'
1701 gr = fixture.create_user_group(gr_name)
1701 gr = fixture.create_user_group(gr_name)
1702 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1702 UserGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN)
1703 Session().commit()
1703 Session().commit()
1704 try:
1704 try:
1705 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1705 id_, params = _build_data(self.apikey, 'remove_user_from_user_group',
1706 usergroupid=gr_name,
1706 usergroupid=gr_name,
1707 userid=TEST_USER_ADMIN_LOGIN)
1707 userid=TEST_USER_ADMIN_LOGIN)
1708 response = api_call(self, params)
1708 response = api_call(self, params)
1709 expected = 'failed to remove member from user group `%s`' % gr_name
1709 expected = 'failed to remove member from user group `%s`' % gr_name
1710 self._compare_error(id_, expected, given=response.body)
1710 self._compare_error(id_, expected, given=response.body)
1711 finally:
1711 finally:
1712 fixture.destroy_user_group(gr_name)
1712 fixture.destroy_user_group(gr_name)
1713
1713
1714 def test_api_delete_user_group(self):
1714 def test_api_delete_user_group(self):
1715 gr_name = u'test_group'
1715 gr_name = u'test_group'
1716 ugroup = fixture.create_user_group(gr_name)
1716 ugroup = fixture.create_user_group(gr_name)
1717 gr_id = ugroup.users_group_id
1717 gr_id = ugroup.users_group_id
1718 try:
1718 try:
1719 id_, params = _build_data(self.apikey, 'delete_user_group',
1719 id_, params = _build_data(self.apikey, 'delete_user_group',
1720 usergroupid=gr_name)
1720 usergroupid=gr_name)
1721 response = api_call(self, params)
1721 response = api_call(self, params)
1722 expected = {
1722 expected = {
1723 'user_group': None,
1723 'user_group': None,
1724 'msg': 'deleted user group ID:%s %s' % (gr_id, gr_name)
1724 'msg': 'deleted user group ID:%s %s' % (gr_id, gr_name)
1725 }
1725 }
1726 self._compare_ok(id_, expected, given=response.body)
1726 self._compare_ok(id_, expected, given=response.body)
1727 finally:
1727 finally:
1728 if UserGroupModel().get_by_name(gr_name):
1728 if UserGroupModel().get_by_name(gr_name):
1729 fixture.destroy_user_group(gr_name)
1729 fixture.destroy_user_group(gr_name)
1730
1730
1731 def test_api_delete_user_group_that_is_assigned(self):
1731 def test_api_delete_user_group_that_is_assigned(self):
1732 gr_name = u'test_group'
1732 gr_name = u'test_group'
1733 ugroup = fixture.create_user_group(gr_name)
1733 ugroup = fixture.create_user_group(gr_name)
1734 gr_id = ugroup.users_group_id
1734 gr_id = ugroup.users_group_id
1735
1735
1736 ugr_to_perm = RepoModel().grant_user_group_permission(self.REPO, gr_name, 'repository.write')
1736 ugr_to_perm = RepoModel().grant_user_group_permission(self.REPO, gr_name, 'repository.write')
1737 msg = 'User Group assigned to %s' % ugr_to_perm.repository.repo_name
1737 msg = 'User Group assigned to %s' % ugr_to_perm.repository.repo_name
1738
1738
1739 try:
1739 try:
1740 id_, params = _build_data(self.apikey, 'delete_user_group',
1740 id_, params = _build_data(self.apikey, 'delete_user_group',
1741 usergroupid=gr_name)
1741 usergroupid=gr_name)
1742 response = api_call(self, params)
1742 response = api_call(self, params)
1743 expected = msg
1743 expected = msg
1744 self._compare_error(id_, expected, given=response.body)
1744 self._compare_error(id_, expected, given=response.body)
1745 finally:
1745 finally:
1746 if UserGroupModel().get_by_name(gr_name):
1746 if UserGroupModel().get_by_name(gr_name):
1747 fixture.destroy_user_group(gr_name)
1747 fixture.destroy_user_group(gr_name)
1748
1748
1749 def test_api_delete_user_group_exception_occurred(self):
1749 def test_api_delete_user_group_exception_occurred(self):
1750 gr_name = u'test_group'
1750 gr_name = u'test_group'
1751 ugroup = fixture.create_user_group(gr_name)
1751 ugroup = fixture.create_user_group(gr_name)
1752 gr_id = ugroup.users_group_id
1752 gr_id = ugroup.users_group_id
1753 id_, params = _build_data(self.apikey, 'delete_user_group',
1753 id_, params = _build_data(self.apikey, 'delete_user_group',
1754 usergroupid=gr_name)
1754 usergroupid=gr_name)
1755
1755
1756 try:
1756 try:
1757 with mock.patch.object(UserGroupModel, 'delete', crash):
1757 with mock.patch.object(UserGroupModel, 'delete', crash):
1758 response = api_call(self, params)
1758 response = api_call(self, params)
1759 expected = 'failed to delete user group ID:%s %s' % (gr_id, gr_name)
1759 expected = 'failed to delete user group ID:%s %s' % (gr_id, gr_name)
1760 self._compare_error(id_, expected, given=response.body)
1760 self._compare_error(id_, expected, given=response.body)
1761 finally:
1761 finally:
1762 fixture.destroy_user_group(gr_name)
1762 fixture.destroy_user_group(gr_name)
1763
1763
1764 @parametrize('name,perm', [
1764 @parametrize('name,perm', [
1765 ('none', 'repository.none'),
1765 ('none', 'repository.none'),
1766 ('read', 'repository.read'),
1766 ('read', 'repository.read'),
1767 ('write', 'repository.write'),
1767 ('write', 'repository.write'),
1768 ('admin', 'repository.admin'),
1768 ('admin', 'repository.admin'),
1769 ])
1769 ])
1770 def test_api_grant_user_permission(self, name, perm):
1770 def test_api_grant_user_permission(self, name, perm):
1771 id_, params = _build_data(self.apikey,
1771 id_, params = _build_data(self.apikey,
1772 'grant_user_permission',
1772 'grant_user_permission',
1773 repoid=self.REPO,
1773 repoid=self.REPO,
1774 userid=TEST_USER_ADMIN_LOGIN,
1774 userid=TEST_USER_ADMIN_LOGIN,
1775 perm=perm)
1775 perm=perm)
1776 response = api_call(self, params)
1776 response = api_call(self, params)
1777
1777
1778 ret = {
1778 ret = {
1779 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1779 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1780 perm, TEST_USER_ADMIN_LOGIN, self.REPO
1780 perm, TEST_USER_ADMIN_LOGIN, self.REPO
1781 ),
1781 ),
1782 'success': True
1782 'success': True
1783 }
1783 }
1784 expected = ret
1784 expected = ret
1785 self._compare_ok(id_, expected, given=response.body)
1785 self._compare_ok(id_, expected, given=response.body)
1786
1786
1787 def test_api_grant_user_permission_wrong_permission(self):
1787 def test_api_grant_user_permission_wrong_permission(self):
1788 perm = 'haha.no.permission'
1788 perm = 'haha.no.permission'
1789 id_, params = _build_data(self.apikey,
1789 id_, params = _build_data(self.apikey,
1790 'grant_user_permission',
1790 'grant_user_permission',
1791 repoid=self.REPO,
1791 repoid=self.REPO,
1792 userid=TEST_USER_ADMIN_LOGIN,
1792 userid=TEST_USER_ADMIN_LOGIN,
1793 perm=perm)
1793 perm=perm)
1794 response = api_call(self, params)
1794 response = api_call(self, params)
1795
1795
1796 expected = 'permission `%s` does not exist' % perm
1796 expected = 'permission `%s` does not exist' % perm
1797 self._compare_error(id_, expected, given=response.body)
1797 self._compare_error(id_, expected, given=response.body)
1798
1798
1799 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
1799 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
1800 def test_api_grant_user_permission_exception_when_adding(self):
1800 def test_api_grant_user_permission_exception_when_adding(self):
1801 perm = 'repository.read'
1801 perm = 'repository.read'
1802 id_, params = _build_data(self.apikey,
1802 id_, params = _build_data(self.apikey,
1803 'grant_user_permission',
1803 'grant_user_permission',
1804 repoid=self.REPO,
1804 repoid=self.REPO,
1805 userid=TEST_USER_ADMIN_LOGIN,
1805 userid=TEST_USER_ADMIN_LOGIN,
1806 perm=perm)
1806 perm=perm)
1807 response = api_call(self, params)
1807 response = api_call(self, params)
1808
1808
1809 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1809 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1810 TEST_USER_ADMIN_LOGIN, self.REPO
1810 TEST_USER_ADMIN_LOGIN, self.REPO
1811 )
1811 )
1812 self._compare_error(id_, expected, given=response.body)
1812 self._compare_error(id_, expected, given=response.body)
1813
1813
1814 def test_api_revoke_user_permission(self):
1814 def test_api_revoke_user_permission(self):
1815 id_, params = _build_data(self.apikey,
1815 id_, params = _build_data(self.apikey,
1816 'revoke_user_permission',
1816 'revoke_user_permission',
1817 repoid=self.REPO,
1817 repoid=self.REPO,
1818 userid=TEST_USER_ADMIN_LOGIN, )
1818 userid=TEST_USER_ADMIN_LOGIN, )
1819 response = api_call(self, params)
1819 response = api_call(self, params)
1820
1820
1821 expected = {
1821 expected = {
1822 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1822 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1823 TEST_USER_ADMIN_LOGIN, self.REPO
1823 TEST_USER_ADMIN_LOGIN, self.REPO
1824 ),
1824 ),
1825 'success': True
1825 'success': True
1826 }
1826 }
1827 self._compare_ok(id_, expected, given=response.body)
1827 self._compare_ok(id_, expected, given=response.body)
1828
1828
1829 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
1829 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
1830 def test_api_revoke_user_permission_exception_when_adding(self):
1830 def test_api_revoke_user_permission_exception_when_adding(self):
1831 id_, params = _build_data(self.apikey,
1831 id_, params = _build_data(self.apikey,
1832 'revoke_user_permission',
1832 'revoke_user_permission',
1833 repoid=self.REPO,
1833 repoid=self.REPO,
1834 userid=TEST_USER_ADMIN_LOGIN, )
1834 userid=TEST_USER_ADMIN_LOGIN, )
1835 response = api_call(self, params)
1835 response = api_call(self, params)
1836
1836
1837 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1837 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
1838 TEST_USER_ADMIN_LOGIN, self.REPO
1838 TEST_USER_ADMIN_LOGIN, self.REPO
1839 )
1839 )
1840 self._compare_error(id_, expected, given=response.body)
1840 self._compare_error(id_, expected, given=response.body)
1841
1841
1842 @parametrize('name,perm', [
1842 @parametrize('name,perm', [
1843 ('none', 'repository.none'),
1843 ('none', 'repository.none'),
1844 ('read', 'repository.read'),
1844 ('read', 'repository.read'),
1845 ('write', 'repository.write'),
1845 ('write', 'repository.write'),
1846 ('admin', 'repository.admin'),
1846 ('admin', 'repository.admin'),
1847 ])
1847 ])
1848 def test_api_grant_user_group_permission(self, name, perm):
1848 def test_api_grant_user_group_permission(self, name, perm):
1849 id_, params = _build_data(self.apikey,
1849 id_, params = _build_data(self.apikey,
1850 'grant_user_group_permission',
1850 'grant_user_group_permission',
1851 repoid=self.REPO,
1851 repoid=self.REPO,
1852 usergroupid=TEST_USER_GROUP,
1852 usergroupid=TEST_USER_GROUP,
1853 perm=perm)
1853 perm=perm)
1854 response = api_call(self, params)
1854 response = api_call(self, params)
1855
1855
1856 ret = {
1856 ret = {
1857 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
1857 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
1858 perm, TEST_USER_GROUP, self.REPO
1858 perm, TEST_USER_GROUP, self.REPO
1859 ),
1859 ),
1860 'success': True
1860 'success': True
1861 }
1861 }
1862 expected = ret
1862 expected = ret
1863 self._compare_ok(id_, expected, given=response.body)
1863 self._compare_ok(id_, expected, given=response.body)
1864
1864
1865 def test_api_grant_user_group_permission_wrong_permission(self):
1865 def test_api_grant_user_group_permission_wrong_permission(self):
1866 perm = 'haha.no.permission'
1866 perm = 'haha.no.permission'
1867 id_, params = _build_data(self.apikey,
1867 id_, params = _build_data(self.apikey,
1868 'grant_user_group_permission',
1868 'grant_user_group_permission',
1869 repoid=self.REPO,
1869 repoid=self.REPO,
1870 usergroupid=TEST_USER_GROUP,
1870 usergroupid=TEST_USER_GROUP,
1871 perm=perm)
1871 perm=perm)
1872 response = api_call(self, params)
1872 response = api_call(self, params)
1873
1873
1874 expected = 'permission `%s` does not exist' % perm
1874 expected = 'permission `%s` does not exist' % perm
1875 self._compare_error(id_, expected, given=response.body)
1875 self._compare_error(id_, expected, given=response.body)
1876
1876
1877 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
1877 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
1878 def test_api_grant_user_group_permission_exception_when_adding(self):
1878 def test_api_grant_user_group_permission_exception_when_adding(self):
1879 perm = 'repository.read'
1879 perm = 'repository.read'
1880 id_, params = _build_data(self.apikey,
1880 id_, params = _build_data(self.apikey,
1881 'grant_user_group_permission',
1881 'grant_user_group_permission',
1882 repoid=self.REPO,
1882 repoid=self.REPO,
1883 usergroupid=TEST_USER_GROUP,
1883 usergroupid=TEST_USER_GROUP,
1884 perm=perm)
1884 perm=perm)
1885 response = api_call(self, params)
1885 response = api_call(self, params)
1886
1886
1887 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1887 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1888 TEST_USER_GROUP, self.REPO
1888 TEST_USER_GROUP, self.REPO
1889 )
1889 )
1890 self._compare_error(id_, expected, given=response.body)
1890 self._compare_error(id_, expected, given=response.body)
1891
1891
1892 def test_api_revoke_user_group_permission(self):
1892 def test_api_revoke_user_group_permission(self):
1893 RepoModel().grant_user_group_permission(repo=self.REPO,
1893 RepoModel().grant_user_group_permission(repo=self.REPO,
1894 group_name=TEST_USER_GROUP,
1894 group_name=TEST_USER_GROUP,
1895 perm='repository.read')
1895 perm='repository.read')
1896 Session().commit()
1896 Session().commit()
1897 id_, params = _build_data(self.apikey,
1897 id_, params = _build_data(self.apikey,
1898 'revoke_user_group_permission',
1898 'revoke_user_group_permission',
1899 repoid=self.REPO,
1899 repoid=self.REPO,
1900 usergroupid=TEST_USER_GROUP, )
1900 usergroupid=TEST_USER_GROUP, )
1901 response = api_call(self, params)
1901 response = api_call(self, params)
1902
1902
1903 expected = {
1903 expected = {
1904 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1904 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1905 TEST_USER_GROUP, self.REPO
1905 TEST_USER_GROUP, self.REPO
1906 ),
1906 ),
1907 'success': True
1907 'success': True
1908 }
1908 }
1909 self._compare_ok(id_, expected, given=response.body)
1909 self._compare_ok(id_, expected, given=response.body)
1910
1910
1911 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
1911 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
1912 def test_api_revoke_user_group_permission_exception_when_adding(self):
1912 def test_api_revoke_user_group_permission_exception_when_adding(self):
1913 id_, params = _build_data(self.apikey,
1913 id_, params = _build_data(self.apikey,
1914 'revoke_user_group_permission',
1914 'revoke_user_group_permission',
1915 repoid=self.REPO,
1915 repoid=self.REPO,
1916 usergroupid=TEST_USER_GROUP, )
1916 usergroupid=TEST_USER_GROUP, )
1917 response = api_call(self, params)
1917 response = api_call(self, params)
1918
1918
1919 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1919 expected = 'failed to edit permission for user group: `%s` in repo: `%s`' % (
1920 TEST_USER_GROUP, self.REPO
1920 TEST_USER_GROUP, self.REPO
1921 )
1921 )
1922 self._compare_error(id_, expected, given=response.body)
1922 self._compare_error(id_, expected, given=response.body)
1923
1923
1924 @parametrize('name,perm,apply_to_children', [
1924 @parametrize('name,perm,apply_to_children', [
1925 ('none', 'group.none', 'none'),
1925 ('none', 'group.none', 'none'),
1926 ('read', 'group.read', 'none'),
1926 ('read', 'group.read', 'none'),
1927 ('write', 'group.write', 'none'),
1927 ('write', 'group.write', 'none'),
1928 ('admin', 'group.admin', 'none'),
1928 ('admin', 'group.admin', 'none'),
1929
1929
1930 ('none', 'group.none', 'all'),
1930 ('none', 'group.none', 'all'),
1931 ('read', 'group.read', 'all'),
1931 ('read', 'group.read', 'all'),
1932 ('write', 'group.write', 'all'),
1932 ('write', 'group.write', 'all'),
1933 ('admin', 'group.admin', 'all'),
1933 ('admin', 'group.admin', 'all'),
1934
1934
1935 ('none', 'group.none', 'repos'),
1935 ('none', 'group.none', 'repos'),
1936 ('read', 'group.read', 'repos'),
1936 ('read', 'group.read', 'repos'),
1937 ('write', 'group.write', 'repos'),
1937 ('write', 'group.write', 'repos'),
1938 ('admin', 'group.admin', 'repos'),
1938 ('admin', 'group.admin', 'repos'),
1939
1939
1940 ('none', 'group.none', 'groups'),
1940 ('none', 'group.none', 'groups'),
1941 ('read', 'group.read', 'groups'),
1941 ('read', 'group.read', 'groups'),
1942 ('write', 'group.write', 'groups'),
1942 ('write', 'group.write', 'groups'),
1943 ('admin', 'group.admin', 'groups'),
1943 ('admin', 'group.admin', 'groups'),
1944 ])
1944 ])
1945 def test_api_grant_user_permission_to_repo_group(self, name, perm, apply_to_children):
1945 def test_api_grant_user_permission_to_repo_group(self, name, perm, apply_to_children):
1946 id_, params = _build_data(self.apikey,
1946 id_, params = _build_data(self.apikey,
1947 'grant_user_permission_to_repo_group',
1947 'grant_user_permission_to_repo_group',
1948 repogroupid=TEST_REPO_GROUP,
1948 repogroupid=TEST_REPO_GROUP,
1949 userid=TEST_USER_ADMIN_LOGIN,
1949 userid=TEST_USER_ADMIN_LOGIN,
1950 perm=perm, apply_to_children=apply_to_children)
1950 perm=perm, apply_to_children=apply_to_children)
1951 response = api_call(self, params)
1951 response = api_call(self, params)
1952
1952
1953 ret = {
1953 ret = {
1954 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1954 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1955 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1955 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1956 ),
1956 ),
1957 'success': True
1957 'success': True
1958 }
1958 }
1959 expected = ret
1959 expected = ret
1960 self._compare_ok(id_, expected, given=response.body)
1960 self._compare_ok(id_, expected, given=response.body)
1961
1961
1962 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
1962 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
1963 ('none_fails', 'group.none', 'none', False, False),
1963 ('none_fails', 'group.none', 'none', False, False),
1964 ('read_fails', 'group.read', 'none', False, False),
1964 ('read_fails', 'group.read', 'none', False, False),
1965 ('write_fails', 'group.write', 'none', False, False),
1965 ('write_fails', 'group.write', 'none', False, False),
1966 ('admin_fails', 'group.admin', 'none', False, False),
1966 ('admin_fails', 'group.admin', 'none', False, False),
1967
1967
1968 # with granted perms
1968 # with granted perms
1969 ('none_ok', 'group.none', 'none', True, True),
1969 ('none_ok', 'group.none', 'none', True, True),
1970 ('read_ok', 'group.read', 'none', True, True),
1970 ('read_ok', 'group.read', 'none', True, True),
1971 ('write_ok', 'group.write', 'none', True, True),
1971 ('write_ok', 'group.write', 'none', True, True),
1972 ('admin_ok', 'group.admin', 'none', True, True),
1972 ('admin_ok', 'group.admin', 'none', True, True),
1973 ])
1973 ])
1974 def test_api_grant_user_permission_to_repo_group_by_regular_user(
1974 def test_api_grant_user_permission_to_repo_group_by_regular_user(
1975 self, name, perm, apply_to_children, grant_admin, access_ok):
1975 self, name, perm, apply_to_children, grant_admin, access_ok):
1976 if grant_admin:
1976 if grant_admin:
1977 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
1977 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
1978 self.TEST_USER_LOGIN,
1978 self.TEST_USER_LOGIN,
1979 'group.admin')
1979 'group.admin')
1980 Session().commit()
1980 Session().commit()
1981
1981
1982 id_, params = _build_data(self.apikey_regular,
1982 id_, params = _build_data(self.apikey_regular,
1983 'grant_user_permission_to_repo_group',
1983 'grant_user_permission_to_repo_group',
1984 repogroupid=TEST_REPO_GROUP,
1984 repogroupid=TEST_REPO_GROUP,
1985 userid=TEST_USER_ADMIN_LOGIN,
1985 userid=TEST_USER_ADMIN_LOGIN,
1986 perm=perm, apply_to_children=apply_to_children)
1986 perm=perm, apply_to_children=apply_to_children)
1987 response = api_call(self, params)
1987 response = api_call(self, params)
1988 if access_ok:
1988 if access_ok:
1989 ret = {
1989 ret = {
1990 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1990 'msg': 'Granted perm: `%s` (recursive:%s) for user: `%s` in repo group: `%s`' % (
1991 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1991 perm, apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
1992 ),
1992 ),
1993 'success': True
1993 'success': True
1994 }
1994 }
1995 expected = ret
1995 expected = ret
1996 self._compare_ok(id_, expected, given=response.body)
1996 self._compare_ok(id_, expected, given=response.body)
1997 else:
1997 else:
1998 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
1998 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
1999 self._compare_error(id_, expected, given=response.body)
1999 self._compare_error(id_, expected, given=response.body)
2000
2000
2001 def test_api_grant_user_permission_to_repo_group_wrong_permission(self):
2001 def test_api_grant_user_permission_to_repo_group_wrong_permission(self):
2002 perm = 'haha.no.permission'
2002 perm = 'haha.no.permission'
2003 id_, params = _build_data(self.apikey,
2003 id_, params = _build_data(self.apikey,
2004 'grant_user_permission_to_repo_group',
2004 'grant_user_permission_to_repo_group',
2005 repogroupid=TEST_REPO_GROUP,
2005 repogroupid=TEST_REPO_GROUP,
2006 userid=TEST_USER_ADMIN_LOGIN,
2006 userid=TEST_USER_ADMIN_LOGIN,
2007 perm=perm)
2007 perm=perm)
2008 response = api_call(self, params)
2008 response = api_call(self, params)
2009
2009
2010 expected = 'permission `%s` does not exist' % perm
2010 expected = 'permission `%s` does not exist' % perm
2011 self._compare_error(id_, expected, given=response.body)
2011 self._compare_error(id_, expected, given=response.body)
2012
2012
2013 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
2013 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
2014 def test_api_grant_user_permission_to_repo_group_exception_when_adding(self):
2014 def test_api_grant_user_permission_to_repo_group_exception_when_adding(self):
2015 perm = 'group.read'
2015 perm = 'group.read'
2016 id_, params = _build_data(self.apikey,
2016 id_, params = _build_data(self.apikey,
2017 'grant_user_permission_to_repo_group',
2017 'grant_user_permission_to_repo_group',
2018 repogroupid=TEST_REPO_GROUP,
2018 repogroupid=TEST_REPO_GROUP,
2019 userid=TEST_USER_ADMIN_LOGIN,
2019 userid=TEST_USER_ADMIN_LOGIN,
2020 perm=perm)
2020 perm=perm)
2021 response = api_call(self, params)
2021 response = api_call(self, params)
2022
2022
2023 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2023 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2024 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2024 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2025 )
2025 )
2026 self._compare_error(id_, expected, given=response.body)
2026 self._compare_error(id_, expected, given=response.body)
2027
2027
2028 @parametrize('name,apply_to_children', [
2028 @parametrize('name,apply_to_children', [
2029 ('none', 'none'),
2029 ('none', 'none'),
2030 ('all', 'all'),
2030 ('all', 'all'),
2031 ('repos', 'repos'),
2031 ('repos', 'repos'),
2032 ('groups', 'groups'),
2032 ('groups', 'groups'),
2033 ])
2033 ])
2034 def test_api_revoke_user_permission_from_repo_group(self, name, apply_to_children):
2034 def test_api_revoke_user_permission_from_repo_group(self, name, apply_to_children):
2035 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2035 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2036 user=TEST_USER_ADMIN_LOGIN,
2036 user=TEST_USER_ADMIN_LOGIN,
2037 perm='group.read',)
2037 perm='group.read',)
2038 Session().commit()
2038 Session().commit()
2039
2039
2040 id_, params = _build_data(self.apikey,
2040 id_, params = _build_data(self.apikey,
2041 'revoke_user_permission_from_repo_group',
2041 'revoke_user_permission_from_repo_group',
2042 repogroupid=TEST_REPO_GROUP,
2042 repogroupid=TEST_REPO_GROUP,
2043 userid=TEST_USER_ADMIN_LOGIN,
2043 userid=TEST_USER_ADMIN_LOGIN,
2044 apply_to_children=apply_to_children,)
2044 apply_to_children=apply_to_children,)
2045 response = api_call(self, params)
2045 response = api_call(self, params)
2046
2046
2047 expected = {
2047 expected = {
2048 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2048 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2049 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2049 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2050 ),
2050 ),
2051 'success': True
2051 'success': True
2052 }
2052 }
2053 self._compare_ok(id_, expected, given=response.body)
2053 self._compare_ok(id_, expected, given=response.body)
2054
2054
2055 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2055 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2056 ('none', 'none', False, False),
2056 ('none', 'none', False, False),
2057 ('all', 'all', False, False),
2057 ('all', 'all', False, False),
2058 ('repos', 'repos', False, False),
2058 ('repos', 'repos', False, False),
2059 ('groups', 'groups', False, False),
2059 ('groups', 'groups', False, False),
2060
2060
2061 # after granting admin rights
2061 # after granting admin rights
2062 ('none', 'none', False, False),
2062 ('none', 'none', False, False),
2063 ('all', 'all', False, False),
2063 ('all', 'all', False, False),
2064 ('repos', 'repos', False, False),
2064 ('repos', 'repos', False, False),
2065 ('groups', 'groups', False, False),
2065 ('groups', 'groups', False, False),
2066 ])
2066 ])
2067 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
2067 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
2068 self, name, apply_to_children, grant_admin, access_ok):
2068 self, name, apply_to_children, grant_admin, access_ok):
2069 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2069 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2070 user=TEST_USER_ADMIN_LOGIN,
2070 user=TEST_USER_ADMIN_LOGIN,
2071 perm='group.read',)
2071 perm='group.read',)
2072 Session().commit()
2072 Session().commit()
2073
2073
2074 if grant_admin:
2074 if grant_admin:
2075 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2075 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2076 self.TEST_USER_LOGIN,
2076 self.TEST_USER_LOGIN,
2077 'group.admin')
2077 'group.admin')
2078 Session().commit()
2078 Session().commit()
2079
2079
2080 id_, params = _build_data(self.apikey_regular,
2080 id_, params = _build_data(self.apikey_regular,
2081 'revoke_user_permission_from_repo_group',
2081 'revoke_user_permission_from_repo_group',
2082 repogroupid=TEST_REPO_GROUP,
2082 repogroupid=TEST_REPO_GROUP,
2083 userid=TEST_USER_ADMIN_LOGIN,
2083 userid=TEST_USER_ADMIN_LOGIN,
2084 apply_to_children=apply_to_children,)
2084 apply_to_children=apply_to_children,)
2085 response = api_call(self, params)
2085 response = api_call(self, params)
2086 if access_ok:
2086 if access_ok:
2087 expected = {
2087 expected = {
2088 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2088 'msg': 'Revoked perm (recursive:%s) for user: `%s` in repo group: `%s`' % (
2089 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2089 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2090 ),
2090 ),
2091 'success': True
2091 'success': True
2092 }
2092 }
2093 self._compare_ok(id_, expected, given=response.body)
2093 self._compare_ok(id_, expected, given=response.body)
2094 else:
2094 else:
2095 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2095 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2096 self._compare_error(id_, expected, given=response.body)
2096 self._compare_error(id_, expected, given=response.body)
2097
2097
2098 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
2098 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
2099 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(self):
2099 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(self):
2100 id_, params = _build_data(self.apikey,
2100 id_, params = _build_data(self.apikey,
2101 'revoke_user_permission_from_repo_group',
2101 'revoke_user_permission_from_repo_group',
2102 repogroupid=TEST_REPO_GROUP,
2102 repogroupid=TEST_REPO_GROUP,
2103 userid=TEST_USER_ADMIN_LOGIN, )
2103 userid=TEST_USER_ADMIN_LOGIN, )
2104 response = api_call(self, params)
2104 response = api_call(self, params)
2105
2105
2106 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2106 expected = 'failed to edit permission for user: `%s` in repo group: `%s`' % (
2107 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2107 TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2108 )
2108 )
2109 self._compare_error(id_, expected, given=response.body)
2109 self._compare_error(id_, expected, given=response.body)
2110
2110
2111 @parametrize('name,perm,apply_to_children', [
2111 @parametrize('name,perm,apply_to_children', [
2112 ('none', 'group.none', 'none'),
2112 ('none', 'group.none', 'none'),
2113 ('read', 'group.read', 'none'),
2113 ('read', 'group.read', 'none'),
2114 ('write', 'group.write', 'none'),
2114 ('write', 'group.write', 'none'),
2115 ('admin', 'group.admin', 'none'),
2115 ('admin', 'group.admin', 'none'),
2116
2116
2117 ('none', 'group.none', 'all'),
2117 ('none', 'group.none', 'all'),
2118 ('read', 'group.read', 'all'),
2118 ('read', 'group.read', 'all'),
2119 ('write', 'group.write', 'all'),
2119 ('write', 'group.write', 'all'),
2120 ('admin', 'group.admin', 'all'),
2120 ('admin', 'group.admin', 'all'),
2121
2121
2122 ('none', 'group.none', 'repos'),
2122 ('none', 'group.none', 'repos'),
2123 ('read', 'group.read', 'repos'),
2123 ('read', 'group.read', 'repos'),
2124 ('write', 'group.write', 'repos'),
2124 ('write', 'group.write', 'repos'),
2125 ('admin', 'group.admin', 'repos'),
2125 ('admin', 'group.admin', 'repos'),
2126
2126
2127 ('none', 'group.none', 'groups'),
2127 ('none', 'group.none', 'groups'),
2128 ('read', 'group.read', 'groups'),
2128 ('read', 'group.read', 'groups'),
2129 ('write', 'group.write', 'groups'),
2129 ('write', 'group.write', 'groups'),
2130 ('admin', 'group.admin', 'groups'),
2130 ('admin', 'group.admin', 'groups'),
2131 ])
2131 ])
2132 def test_api_grant_user_group_permission_to_repo_group(self, name, perm, apply_to_children):
2132 def test_api_grant_user_group_permission_to_repo_group(self, name, perm, apply_to_children):
2133 id_, params = _build_data(self.apikey,
2133 id_, params = _build_data(self.apikey,
2134 'grant_user_group_permission_to_repo_group',
2134 'grant_user_group_permission_to_repo_group',
2135 repogroupid=TEST_REPO_GROUP,
2135 repogroupid=TEST_REPO_GROUP,
2136 usergroupid=TEST_USER_GROUP,
2136 usergroupid=TEST_USER_GROUP,
2137 perm=perm,
2137 perm=perm,
2138 apply_to_children=apply_to_children,)
2138 apply_to_children=apply_to_children,)
2139 response = api_call(self, params)
2139 response = api_call(self, params)
2140
2140
2141 ret = {
2141 ret = {
2142 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2142 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2143 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2143 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2144 ),
2144 ),
2145 'success': True
2145 'success': True
2146 }
2146 }
2147 expected = ret
2147 expected = ret
2148 self._compare_ok(id_, expected, given=response.body)
2148 self._compare_ok(id_, expected, given=response.body)
2149
2149
2150 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
2150 @parametrize('name,perm,apply_to_children,grant_admin,access_ok', [
2151 ('none_fails', 'group.none', 'none', False, False),
2151 ('none_fails', 'group.none', 'none', False, False),
2152 ('read_fails', 'group.read', 'none', False, False),
2152 ('read_fails', 'group.read', 'none', False, False),
2153 ('write_fails', 'group.write', 'none', False, False),
2153 ('write_fails', 'group.write', 'none', False, False),
2154 ('admin_fails', 'group.admin', 'none', False, False),
2154 ('admin_fails', 'group.admin', 'none', False, False),
2155
2155
2156 # with granted perms
2156 # with granted perms
2157 ('none_ok', 'group.none', 'none', True, True),
2157 ('none_ok', 'group.none', 'none', True, True),
2158 ('read_ok', 'group.read', 'none', True, True),
2158 ('read_ok', 'group.read', 'none', True, True),
2159 ('write_ok', 'group.write', 'none', True, True),
2159 ('write_ok', 'group.write', 'none', True, True),
2160 ('admin_ok', 'group.admin', 'none', True, True),
2160 ('admin_ok', 'group.admin', 'none', True, True),
2161 ])
2161 ])
2162 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
2162 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
2163 self, name, perm, apply_to_children, grant_admin, access_ok):
2163 self, name, perm, apply_to_children, grant_admin, access_ok):
2164 if grant_admin:
2164 if grant_admin:
2165 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2165 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2166 self.TEST_USER_LOGIN,
2166 self.TEST_USER_LOGIN,
2167 'group.admin')
2167 'group.admin')
2168 Session().commit()
2168 Session().commit()
2169
2169
2170 id_, params = _build_data(self.apikey_regular,
2170 id_, params = _build_data(self.apikey_regular,
2171 'grant_user_group_permission_to_repo_group',
2171 'grant_user_group_permission_to_repo_group',
2172 repogroupid=TEST_REPO_GROUP,
2172 repogroupid=TEST_REPO_GROUP,
2173 usergroupid=TEST_USER_GROUP,
2173 usergroupid=TEST_USER_GROUP,
2174 perm=perm,
2174 perm=perm,
2175 apply_to_children=apply_to_children,)
2175 apply_to_children=apply_to_children,)
2176 response = api_call(self, params)
2176 response = api_call(self, params)
2177 if access_ok:
2177 if access_ok:
2178 ret = {
2178 ret = {
2179 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2179 'msg': 'Granted perm: `%s` (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2180 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2180 perm, apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2181 ),
2181 ),
2182 'success': True
2182 'success': True
2183 }
2183 }
2184 expected = ret
2184 expected = ret
2185 self._compare_ok(id_, expected, given=response.body)
2185 self._compare_ok(id_, expected, given=response.body)
2186 else:
2186 else:
2187 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2187 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2188 self._compare_error(id_, expected, given=response.body)
2188 self._compare_error(id_, expected, given=response.body)
2189
2189
2190 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(self):
2190 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(self):
2191 perm = 'haha.no.permission'
2191 perm = 'haha.no.permission'
2192 id_, params = _build_data(self.apikey,
2192 id_, params = _build_data(self.apikey,
2193 'grant_user_group_permission_to_repo_group',
2193 'grant_user_group_permission_to_repo_group',
2194 repogroupid=TEST_REPO_GROUP,
2194 repogroupid=TEST_REPO_GROUP,
2195 usergroupid=TEST_USER_GROUP,
2195 usergroupid=TEST_USER_GROUP,
2196 perm=perm)
2196 perm=perm)
2197 response = api_call(self, params)
2197 response = api_call(self, params)
2198
2198
2199 expected = 'permission `%s` does not exist' % perm
2199 expected = 'permission `%s` does not exist' % perm
2200 self._compare_error(id_, expected, given=response.body)
2200 self._compare_error(id_, expected, given=response.body)
2201
2201
2202 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
2202 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
2203 def test_api_grant_user_group_permission_exception_when_adding_to_repo_group(self):
2203 def test_api_grant_user_group_permission_exception_when_adding_to_repo_group(self):
2204 perm = 'group.read'
2204 perm = 'group.read'
2205 id_, params = _build_data(self.apikey,
2205 id_, params = _build_data(self.apikey,
2206 'grant_user_group_permission_to_repo_group',
2206 'grant_user_group_permission_to_repo_group',
2207 repogroupid=TEST_REPO_GROUP,
2207 repogroupid=TEST_REPO_GROUP,
2208 usergroupid=TEST_USER_GROUP,
2208 usergroupid=TEST_USER_GROUP,
2209 perm=perm)
2209 perm=perm)
2210 response = api_call(self, params)
2210 response = api_call(self, params)
2211
2211
2212 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2212 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2213 TEST_USER_GROUP, TEST_REPO_GROUP
2213 TEST_USER_GROUP, TEST_REPO_GROUP
2214 )
2214 )
2215 self._compare_error(id_, expected, given=response.body)
2215 self._compare_error(id_, expected, given=response.body)
2216
2216
2217 @parametrize('name,apply_to_children', [
2217 @parametrize('name,apply_to_children', [
2218 ('none', 'none'),
2218 ('none', 'none'),
2219 ('all', 'all'),
2219 ('all', 'all'),
2220 ('repos', 'repos'),
2220 ('repos', 'repos'),
2221 ('groups', 'groups'),
2221 ('groups', 'groups'),
2222 ])
2222 ])
2223 def test_api_revoke_user_group_permission_from_repo_group(self, name, apply_to_children):
2223 def test_api_revoke_user_group_permission_from_repo_group(self, name, apply_to_children):
2224 RepoGroupModel().grant_user_group_permission(repo_group=TEST_REPO_GROUP,
2224 RepoGroupModel().grant_user_group_permission(repo_group=TEST_REPO_GROUP,
2225 group_name=TEST_USER_GROUP,
2225 group_name=TEST_USER_GROUP,
2226 perm='group.read',)
2226 perm='group.read',)
2227 Session().commit()
2227 Session().commit()
2228 id_, params = _build_data(self.apikey,
2228 id_, params = _build_data(self.apikey,
2229 'revoke_user_group_permission_from_repo_group',
2229 'revoke_user_group_permission_from_repo_group',
2230 repogroupid=TEST_REPO_GROUP,
2230 repogroupid=TEST_REPO_GROUP,
2231 usergroupid=TEST_USER_GROUP,
2231 usergroupid=TEST_USER_GROUP,
2232 apply_to_children=apply_to_children,)
2232 apply_to_children=apply_to_children,)
2233 response = api_call(self, params)
2233 response = api_call(self, params)
2234
2234
2235 expected = {
2235 expected = {
2236 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2236 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2237 apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2237 apply_to_children, TEST_USER_GROUP, TEST_REPO_GROUP
2238 ),
2238 ),
2239 'success': True
2239 'success': True
2240 }
2240 }
2241 self._compare_ok(id_, expected, given=response.body)
2241 self._compare_ok(id_, expected, given=response.body)
2242
2242
2243 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2243 @parametrize('name,apply_to_children,grant_admin,access_ok', [
2244 ('none', 'none', False, False),
2244 ('none', 'none', False, False),
2245 ('all', 'all', False, False),
2245 ('all', 'all', False, False),
2246 ('repos', 'repos', False, False),
2246 ('repos', 'repos', False, False),
2247 ('groups', 'groups', False, False),
2247 ('groups', 'groups', False, False),
2248
2248
2249 # after granting admin rights
2249 # after granting admin rights
2250 ('none', 'none', False, False),
2250 ('none', 'none', False, False),
2251 ('all', 'all', False, False),
2251 ('all', 'all', False, False),
2252 ('repos', 'repos', False, False),
2252 ('repos', 'repos', False, False),
2253 ('groups', 'groups', False, False),
2253 ('groups', 'groups', False, False),
2254 ])
2254 ])
2255 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
2255 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
2256 self, name, apply_to_children, grant_admin, access_ok):
2256 self, name, apply_to_children, grant_admin, access_ok):
2257 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2257 RepoGroupModel().grant_user_permission(repo_group=TEST_REPO_GROUP,
2258 user=TEST_USER_ADMIN_LOGIN,
2258 user=TEST_USER_ADMIN_LOGIN,
2259 perm='group.read',)
2259 perm='group.read',)
2260 Session().commit()
2260 Session().commit()
2261
2261
2262 if grant_admin:
2262 if grant_admin:
2263 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2263 RepoGroupModel().grant_user_permission(TEST_REPO_GROUP,
2264 self.TEST_USER_LOGIN,
2264 self.TEST_USER_LOGIN,
2265 'group.admin')
2265 'group.admin')
2266 Session().commit()
2266 Session().commit()
2267
2267
2268 id_, params = _build_data(self.apikey_regular,
2268 id_, params = _build_data(self.apikey_regular,
2269 'revoke_user_group_permission_from_repo_group',
2269 'revoke_user_group_permission_from_repo_group',
2270 repogroupid=TEST_REPO_GROUP,
2270 repogroupid=TEST_REPO_GROUP,
2271 usergroupid=TEST_USER_GROUP,
2271 usergroupid=TEST_USER_GROUP,
2272 apply_to_children=apply_to_children,)
2272 apply_to_children=apply_to_children,)
2273 response = api_call(self, params)
2273 response = api_call(self, params)
2274 if access_ok:
2274 if access_ok:
2275 expected = {
2275 expected = {
2276 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2276 'msg': 'Revoked perm (recursive:%s) for user group: `%s` in repo group: `%s`' % (
2277 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2277 apply_to_children, TEST_USER_ADMIN_LOGIN, TEST_REPO_GROUP
2278 ),
2278 ),
2279 'success': True
2279 'success': True
2280 }
2280 }
2281 self._compare_ok(id_, expected, given=response.body)
2281 self._compare_ok(id_, expected, given=response.body)
2282 else:
2282 else:
2283 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2283 expected = 'repository group `%s` does not exist' % TEST_REPO_GROUP
2284 self._compare_error(id_, expected, given=response.body)
2284 self._compare_error(id_, expected, given=response.body)
2285
2285
2286 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
2286 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
2287 def test_api_revoke_user_group_permission_from_repo_group_exception_when_adding(self):
2287 def test_api_revoke_user_group_permission_from_repo_group_exception_when_adding(self):
2288 id_, params = _build_data(self.apikey, 'revoke_user_group_permission_from_repo_group',
2288 id_, params = _build_data(self.apikey, 'revoke_user_group_permission_from_repo_group',
2289 repogroupid=TEST_REPO_GROUP,
2289 repogroupid=TEST_REPO_GROUP,
2290 usergroupid=TEST_USER_GROUP,)
2290 usergroupid=TEST_USER_GROUP,)
2291 response = api_call(self, params)
2291 response = api_call(self, params)
2292
2292
2293 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2293 expected = 'failed to edit permission for user group: `%s` in repo group: `%s`' % (
2294 TEST_USER_GROUP, TEST_REPO_GROUP
2294 TEST_USER_GROUP, TEST_REPO_GROUP
2295 )
2295 )
2296 self._compare_error(id_, expected, given=response.body)
2296 self._compare_error(id_, expected, given=response.body)
2297
2297
2298 def test_api_get_gist(self):
2298 def test_api_get_gist(self):
2299 gist = fixture.create_gist()
2299 gist = fixture.create_gist()
2300 gist_id = gist.gist_access_id
2300 gist_id = gist.gist_access_id
2301 gist_created_on = gist.created_on
2301 gist_created_on = gist.created_on
2302 id_, params = _build_data(self.apikey, 'get_gist',
2302 id_, params = _build_data(self.apikey, 'get_gist',
2303 gistid=gist_id, )
2303 gistid=gist_id, )
2304 response = api_call(self, params)
2304 response = api_call(self, params)
2305
2305
2306 expected = {
2306 expected = {
2307 'access_id': gist_id,
2307 'access_id': gist_id,
2308 'created_on': gist_created_on,
2308 'created_on': gist_created_on,
2309 'description': 'new-gist',
2309 'description': 'new-gist',
2310 'expires': -1.0,
2310 'expires': -1.0,
2311 'gist_id': int(gist_id),
2311 'gist_id': int(gist_id),
2312 'type': 'public',
2312 'type': 'public',
2313 'url': 'http://localhost:80/_admin/gists/%s' % gist_id
2313 'url': 'http://localhost:80/_admin/gists/%s' % gist_id
2314 }
2314 }
2315
2315
2316 self._compare_ok(id_, expected, given=response.body)
2316 self._compare_ok(id_, expected, given=response.body)
2317
2317
2318 def test_api_get_gist_that_does_not_exist(self):
2318 def test_api_get_gist_that_does_not_exist(self):
2319 id_, params = _build_data(self.apikey_regular, 'get_gist',
2319 id_, params = _build_data(self.apikey_regular, 'get_gist',
2320 gistid='12345', )
2320 gistid='12345', )
2321 response = api_call(self, params)
2321 response = api_call(self, params)
2322 expected = 'gist `%s` does not exist' % ('12345',)
2322 expected = 'gist `%s` does not exist' % ('12345',)
2323 self._compare_error(id_, expected, given=response.body)
2323 self._compare_error(id_, expected, given=response.body)
2324
2324
2325 def test_api_get_gist_private_gist_without_permission(self):
2325 def test_api_get_gist_private_gist_without_permission(self):
2326 gist = fixture.create_gist()
2326 gist = fixture.create_gist()
2327 gist_id = gist.gist_access_id
2327 gist_id = gist.gist_access_id
2328 gist_created_on = gist.created_on
2328 gist_created_on = gist.created_on
2329 id_, params = _build_data(self.apikey_regular, 'get_gist',
2329 id_, params = _build_data(self.apikey_regular, 'get_gist',
2330 gistid=gist_id, )
2330 gistid=gist_id, )
2331 response = api_call(self, params)
2331 response = api_call(self, params)
2332
2332
2333 expected = 'gist `%s` does not exist' % gist_id
2333 expected = 'gist `%s` does not exist' % gist_id
2334 self._compare_error(id_, expected, given=response.body)
2334 self._compare_error(id_, expected, given=response.body)
2335
2335
2336 def test_api_get_gists(self):
2336 def test_api_get_gists(self):
2337 fixture.create_gist()
2337 fixture.create_gist()
2338 fixture.create_gist()
2338 fixture.create_gist()
2339
2339
2340 id_, params = _build_data(self.apikey, 'get_gists')
2340 id_, params = _build_data(self.apikey, 'get_gists')
2341 response = api_call(self, params)
2341 response = api_call(self, params)
2342 expected = response.json
2342 expected = response.json
2343 assert len(response.json['result']) == 2
2343 assert len(response.json['result']) == 2
2344 #self._compare_ok(id_, expected, given=response.body)
2344 #self._compare_ok(id_, expected, given=response.body)
2345
2345
2346 def test_api_get_gists_regular_user(self):
2346 def test_api_get_gists_regular_user(self):
2347 # by admin
2347 # by admin
2348 fixture.create_gist()
2348 fixture.create_gist()
2349 fixture.create_gist()
2349 fixture.create_gist()
2350
2350
2351 # by reg user
2351 # by reg user
2352 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2352 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2353 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2353 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2354 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2354 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2355
2355
2356 id_, params = _build_data(self.apikey_regular, 'get_gists')
2356 id_, params = _build_data(self.apikey_regular, 'get_gists')
2357 response = api_call(self, params)
2357 response = api_call(self, params)
2358 expected = response.json
2358 expected = response.json
2359 assert len(response.json['result']) == 3
2359 assert len(response.json['result']) == 3
2360 #self._compare_ok(id_, expected, given=response.body)
2360 #self._compare_ok(id_, expected, given=response.body)
2361
2361
2362 def test_api_get_gists_only_for_regular_user(self):
2362 def test_api_get_gists_only_for_regular_user(self):
2363 # by admin
2363 # by admin
2364 fixture.create_gist()
2364 fixture.create_gist()
2365 fixture.create_gist()
2365 fixture.create_gist()
2366
2366
2367 # by reg user
2367 # by reg user
2368 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2368 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2369 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2369 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2370 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2370 fixture.create_gist(owner=self.TEST_USER_LOGIN)
2371
2371
2372 id_, params = _build_data(self.apikey, 'get_gists',
2372 id_, params = _build_data(self.apikey, 'get_gists',
2373 userid=self.TEST_USER_LOGIN)
2373 userid=self.TEST_USER_LOGIN)
2374 response = api_call(self, params)
2374 response = api_call(self, params)
2375 expected = response.json
2375 expected = response.json
2376 assert len(response.json['result']) == 3
2376 assert len(response.json['result']) == 3
2377 #self._compare_ok(id_, expected, given=response.body)
2377 #self._compare_ok(id_, expected, given=response.body)
2378
2378
2379 def test_api_get_gists_regular_user_with_different_userid(self):
2379 def test_api_get_gists_regular_user_with_different_userid(self):
2380 id_, params = _build_data(self.apikey_regular, 'get_gists',
2380 id_, params = _build_data(self.apikey_regular, 'get_gists',
2381 userid=TEST_USER_ADMIN_LOGIN)
2381 userid=TEST_USER_ADMIN_LOGIN)
2382 response = api_call(self, params)
2382 response = api_call(self, params)
2383 expected = 'userid is not the same as your user'
2383 expected = 'userid is not the same as your user'
2384 self._compare_error(id_, expected, given=response.body)
2384 self._compare_error(id_, expected, given=response.body)
2385
2385
2386 def test_api_create_gist(self):
2386 def test_api_create_gist(self):
2387 id_, params = _build_data(self.apikey_regular, 'create_gist',
2387 id_, params = _build_data(self.apikey_regular, 'create_gist',
2388 lifetime=10,
2388 lifetime=10,
2389 description='foobar-gist',
2389 description='foobar-gist',
2390 gist_type='public',
2390 gist_type='public',
2391 files={'foobar': {'content': 'foo'}})
2391 files={'foobar': {'content': 'foo'}})
2392 response = api_call(self, params)
2392 response = api_call(self, params)
2393 response_json = response.json
2393 response_json = response.json
2394 expected = {
2394 expected = {
2395 'gist': {
2395 'gist': {
2396 'access_id': response_json['result']['gist']['access_id'],
2396 'access_id': response_json['result']['gist']['access_id'],
2397 'created_on': response_json['result']['gist']['created_on'],
2397 'created_on': response_json['result']['gist']['created_on'],
2398 'description': 'foobar-gist',
2398 'description': 'foobar-gist',
2399 'expires': response_json['result']['gist']['expires'],
2399 'expires': response_json['result']['gist']['expires'],
2400 'gist_id': response_json['result']['gist']['gist_id'],
2400 'gist_id': response_json['result']['gist']['gist_id'],
2401 'type': 'public',
2401 'type': 'public',
2402 'url': response_json['result']['gist']['url']
2402 'url': response_json['result']['gist']['url']
2403 },
2403 },
2404 'msg': 'created new gist'
2404 'msg': 'created new gist'
2405 }
2405 }
2406 self._compare_ok(id_, expected, given=response.body)
2406 self._compare_ok(id_, expected, given=response.body)
2407
2407
2408 @mock.patch.object(GistModel, 'create', crash)
2408 @mock.patch.object(GistModel, 'create', crash)
2409 def test_api_create_gist_exception_occurred(self):
2409 def test_api_create_gist_exception_occurred(self):
2410 id_, params = _build_data(self.apikey_regular, 'create_gist',
2410 id_, params = _build_data(self.apikey_regular, 'create_gist',
2411 files={})
2411 files={})
2412 response = api_call(self, params)
2412 response = api_call(self, params)
2413 expected = 'failed to create gist'
2413 expected = 'failed to create gist'
2414 self._compare_error(id_, expected, given=response.body)
2414 self._compare_error(id_, expected, given=response.body)
2415
2415
2416 def test_api_delete_gist(self):
2416 def test_api_delete_gist(self):
2417 gist_id = fixture.create_gist().gist_access_id
2417 gist_id = fixture.create_gist().gist_access_id
2418 id_, params = _build_data(self.apikey, 'delete_gist',
2418 id_, params = _build_data(self.apikey, 'delete_gist',
2419 gistid=gist_id)
2419 gistid=gist_id)
2420 response = api_call(self, params)
2420 response = api_call(self, params)
2421 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2421 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2422 self._compare_ok(id_, expected, given=response.body)
2422 self._compare_ok(id_, expected, given=response.body)
2423
2423
2424 def test_api_delete_gist_regular_user(self):
2424 def test_api_delete_gist_regular_user(self):
2425 gist_id = fixture.create_gist(owner=self.TEST_USER_LOGIN).gist_access_id
2425 gist_id = fixture.create_gist(owner=self.TEST_USER_LOGIN).gist_access_id
2426 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2426 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2427 gistid=gist_id)
2427 gistid=gist_id)
2428 response = api_call(self, params)
2428 response = api_call(self, params)
2429 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2429 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % gist_id}
2430 self._compare_ok(id_, expected, given=response.body)
2430 self._compare_ok(id_, expected, given=response.body)
2431
2431
2432 def test_api_delete_gist_regular_user_no_permission(self):
2432 def test_api_delete_gist_regular_user_no_permission(self):
2433 gist_id = fixture.create_gist().gist_access_id
2433 gist_id = fixture.create_gist().gist_access_id
2434 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2434 id_, params = _build_data(self.apikey_regular, 'delete_gist',
2435 gistid=gist_id)
2435 gistid=gist_id)
2436 response = api_call(self, params)
2436 response = api_call(self, params)
2437 expected = 'gist `%s` does not exist' % (gist_id,)
2437 expected = 'gist `%s` does not exist' % (gist_id,)
2438 self._compare_error(id_, expected, given=response.body)
2438 self._compare_error(id_, expected, given=response.body)
2439
2439
2440 @mock.patch.object(GistModel, 'delete', crash)
2440 @mock.patch.object(GistModel, 'delete', crash)
2441 def test_api_delete_gist_exception_occurred(self):
2441 def test_api_delete_gist_exception_occurred(self):
2442 gist_id = fixture.create_gist().gist_access_id
2442 gist_id = fixture.create_gist().gist_access_id
2443 id_, params = _build_data(self.apikey, 'delete_gist',
2443 id_, params = _build_data(self.apikey, 'delete_gist',
2444 gistid=gist_id)
2444 gistid=gist_id)
2445 response = api_call(self, params)
2445 response = api_call(self, params)
2446 expected = 'failed to delete gist ID:%s' % (gist_id,)
2446 expected = 'failed to delete gist ID:%s' % (gist_id,)
2447 self._compare_error(id_, expected, given=response.body)
2447 self._compare_error(id_, expected, given=response.body)
2448
2448
2449 def test_api_get_ip(self):
2449 def test_api_get_ip(self):
2450 id_, params = _build_data(self.apikey, 'get_ip')
2450 id_, params = _build_data(self.apikey, 'get_ip')
2451 response = api_call(self, params)
2451 response = api_call(self, params)
2452 expected = {
2452 expected = {
2453 'server_ip_addr': '0.0.0.0',
2453 'server_ip_addr': '0.0.0.0',
2454 'user_ips': []
2454 'user_ips': []
2455 }
2455 }
2456 self._compare_ok(id_, expected, given=response.body)
2456 self._compare_ok(id_, expected, given=response.body)
2457
2457
2458 def test_api_get_server_info(self):
2458 def test_api_get_server_info(self):
2459 id_, params = _build_data(self.apikey, 'get_server_info')
2459 id_, params = _build_data(self.apikey, 'get_server_info')
2460 response = api_call(self, params)
2460 response = api_call(self, params)
2461 expected = Setting.get_server_info()
2461 expected = Setting.get_server_info()
2462 self._compare_ok(id_, expected, given=response.body)
2462 self._compare_ok(id_, expected, given=response.body)
2463
2463
2464 def test_api_get_changesets(self):
2465 id_, params = _build_data(self.apikey, 'get_changesets',
2466 repoid=self.REPO, start=0, end=2)
2467 response = api_call(self, params)
2468 result = json.loads(response.body)["result"]
2469 assert len(result) == 3
2470 assert result[0].has_key('message')
2471 assert not result[0].has_key('added')
2472
2473 def test_api_get_changesets_with_file_list(self):
2474 id_, params = _build_data(self.apikey, 'get_changesets',
2475 repoid=self.REPO, start_date="2010-04-07T23:30:30", end_date="2010-04-08T00:31:14", with_file_list=True)
2476 response = api_call(self, params)
2477 result = json.loads(response.body)["result"]
2478 assert len(result) == 3
2479 assert result[0].has_key('message')
2480 assert result[0].has_key('added')
2481
2464 def test_api_get_changeset(self):
2482 def test_api_get_changeset(self):
2465 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2483 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2466 id_, params = _build_data(self.apikey, 'get_changeset',
2484 id_, params = _build_data(self.apikey, 'get_changeset',
2467 repoid=self.REPO, raw_id = self.TEST_REVISION)
2485 repoid=self.REPO, raw_id = self.TEST_REVISION)
2468 response = api_call(self, params)
2486 response = api_call(self, params)
2469 result = json.loads(response.body)["result"]
2487 result = json.loads(response.body)["result"]
2470 assert result["raw_id"] == self.TEST_REVISION
2488 assert result["raw_id"] == self.TEST_REVISION
2471 assert not result.has_key("reviews")
2489 assert not result.has_key("reviews")
2472
2490
2473 def test_api_get_changeset_with_reviews(self):
2491 def test_api_get_changeset_with_reviews(self):
2474 reviewobjs = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2492 reviewobjs = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2475 id_, params = _build_data(self.apikey, 'get_changeset',
2493 id_, params = _build_data(self.apikey, 'get_changeset',
2476 repoid=self.REPO, raw_id = self.TEST_REVISION,
2494 repoid=self.REPO, raw_id = self.TEST_REVISION,
2477 with_reviews = True)
2495 with_reviews = True)
2478 response = api_call(self, params)
2496 response = api_call(self, params)
2479 result = json.loads(response.body)["result"]
2497 result = json.loads(response.body)["result"]
2480 assert result["raw_id"] == self.TEST_REVISION
2498 assert result["raw_id"] == self.TEST_REVISION
2481 assert result.has_key("reviews")
2499 assert result.has_key("reviews")
2482 assert len(result["reviews"]) == 1
2500 assert len(result["reviews"]) == 1
2483 review = result["reviews"][0]
2501 review = result["reviews"][0]
2484 expected = {
2502 expected = {
2485 'status': 'approved',
2503 'status': 'approved',
2486 'modified_at': reviewobjs[0].modified_at.isoformat()[:-3],
2504 'modified_at': reviewobjs[0].modified_at.isoformat()[:-3],
2487 'reviewer': 'test_admin',
2505 'reviewer': 'test_admin',
2488 }
2506 }
2489 assert review == expected
2507 assert review == expected
2490
2508
2491 def test_api_get_changeset_that_does_not_exist(self):
2509 def test_api_get_changeset_that_does_not_exist(self):
2492 """ Fetch changeset status for non-existant changeset.
2510 """ Fetch changeset status for non-existant changeset.
2493 revision id is the above git hash used in the test above with the
2511 revision id is the above git hash used in the test above with the
2494 last 3 nibbles replaced with 0xf. Should not exist for git _or_ hg.
2512 last 3 nibbles replaced with 0xf. Should not exist for git _or_ hg.
2495 """
2513 """
2496 id_, params = _build_data(self.apikey, 'get_changeset',
2514 id_, params = _build_data(self.apikey, 'get_changeset',
2497 repoid=self.REPO, raw_id = '7ab37bc680b4aa72c34d07b230c866c28e9fcfff')
2515 repoid=self.REPO, raw_id = '7ab37bc680b4aa72c34d07b230c866c28e9fcfff')
2498 response = api_call(self, params)
2516 response = api_call(self, params)
2499 expected = u'Changeset %s does not exist' % ('7ab37bc680b4aa72c34d07b230c866c28e9fcfff',)
2517 expected = u'Changeset %s does not exist' % ('7ab37bc680b4aa72c34d07b230c866c28e9fcfff',)
2500 self._compare_error(id_, expected, given=response.body)
2518 self._compare_error(id_, expected, given=response.body)
2501
2519
2502 def test_api_get_changeset_without_permission(self):
2520 def test_api_get_changeset_without_permission(self):
2503 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2521 review = fixture.review_changeset(self.REPO, self.TEST_REVISION, "approved")
2504 RepoModel().revoke_user_permission(repo=self.REPO, user=self.TEST_USER_LOGIN)
2522 RepoModel().revoke_user_permission(repo=self.REPO, user=self.TEST_USER_LOGIN)
2505 RepoModel().revoke_user_permission(repo=self.REPO, user="default")
2523 RepoModel().revoke_user_permission(repo=self.REPO, user="default")
2506 id_, params = _build_data(self.apikey_regular, 'get_changeset',
2524 id_, params = _build_data(self.apikey_regular, 'get_changeset',
2507 repoid=self.REPO, raw_id = self.TEST_REVISION)
2525 repoid=self.REPO, raw_id = self.TEST_REVISION)
2508 response = api_call(self, params)
2526 response = api_call(self, params)
2509 expected = u'Access denied to repo %s' % self.REPO
2527 expected = u'Access denied to repo %s' % self.REPO
2510 self._compare_error(id_, expected, given=response.body)
2528 self._compare_error(id_, expected, given=response.body)
2511
2529
2512 def test_api_get_pullrequest(self):
2530 def test_api_get_pullrequest(self):
2513 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'get test')
2531 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'get test')
2514 random_id = random.randrange(1, 9999)
2532 random_id = random.randrange(1, 9999)
2515 params = json.dumps({
2533 params = json.dumps({
2516 "id": random_id,
2534 "id": random_id,
2517 "api_key": self.apikey,
2535 "api_key": self.apikey,
2518 "method": 'get_pullrequest',
2536 "method": 'get_pullrequest',
2519 "args": {"pullrequest_id": pull_request_id},
2537 "args": {"pullrequest_id": pull_request_id},
2520 })
2538 })
2521 response = api_call(self, params)
2539 response = api_call(self, params)
2522 pullrequest = PullRequest().get(pull_request_id)
2540 pullrequest = PullRequest().get(pull_request_id)
2523 expected = {
2541 expected = {
2524 "status": "new",
2542 "status": "new",
2525 "pull_request_id": pull_request_id,
2543 "pull_request_id": pull_request_id,
2526 "description": "No description",
2544 "description": "No description",
2527 "url": "/%s/pull-request/%s/_/%s" % (self.REPO, pull_request_id, "stable"),
2545 "url": "/%s/pull-request/%s/_/%s" % (self.REPO, pull_request_id, "stable"),
2528 "reviewers": [{"username": "test_regular"}],
2546 "reviewers": [{"username": "test_regular"}],
2529 "org_repo_url": "http://localhost:80/%s" % self.REPO,
2547 "org_repo_url": "http://localhost:80/%s" % self.REPO,
2530 "org_ref_parts": ["branch", "stable", self.TEST_PR_SRC],
2548 "org_ref_parts": ["branch", "stable", self.TEST_PR_SRC],
2531 "other_ref_parts": ["branch", "default", self.TEST_PR_DST],
2549 "other_ref_parts": ["branch", "default", self.TEST_PR_DST],
2532 "comments": [{"username": TEST_USER_ADMIN_LOGIN, "text": "",
2550 "comments": [{"username": TEST_USER_ADMIN_LOGIN, "text": "",
2533 "comment_id": pullrequest.comments[0].comment_id}],
2551 "comment_id": pullrequest.comments[0].comment_id}],
2534 "owner": TEST_USER_ADMIN_LOGIN,
2552 "owner": TEST_USER_ADMIN_LOGIN,
2535 "statuses": [{"status": "under_review", "reviewer": TEST_USER_ADMIN_LOGIN, "modified_at": "2000-01-01T00:00:00.000"} for i in range(0, len(self.TEST_PR_REVISIONS))],
2553 "statuses": [{"status": "under_review", "reviewer": TEST_USER_ADMIN_LOGIN, "modified_at": "2000-01-01T00:00:00.000"} for i in range(0, len(self.TEST_PR_REVISIONS))],
2536 "title": "get test",
2554 "title": "get test",
2537 "revisions": self.TEST_PR_REVISIONS,
2555 "revisions": self.TEST_PR_REVISIONS,
2538 }
2556 }
2539 self._compare_ok(random_id, expected,
2557 self._compare_ok(random_id, expected,
2540 given=re.sub("\d\d\d\d\-\d\d\-\d\dT\d\d\:\d\d\:\d\d\.\d\d\d",
2558 given=re.sub("\d\d\d\d\-\d\d\-\d\dT\d\d\:\d\d\:\d\d\.\d\d\d",
2541 "2000-01-01T00:00:00.000", response.body))
2559 "2000-01-01T00:00:00.000", response.body))
2542
2560
2543 def test_api_close_pullrequest(self):
2561 def test_api_close_pullrequest(self):
2544 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'close test')
2562 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u'close test')
2545 random_id = random.randrange(1, 9999)
2563 random_id = random.randrange(1, 9999)
2546 params = json.dumps({
2564 params = json.dumps({
2547 "id": random_id,
2565 "id": random_id,
2548 "api_key": self.apikey,
2566 "api_key": self.apikey,
2549 "method": "comment_pullrequest",
2567 "method": "comment_pullrequest",
2550 "args": {"pull_request_id": pull_request_id, "close_pr": True},
2568 "args": {"pull_request_id": pull_request_id, "close_pr": True},
2551 })
2569 })
2552 response = api_call(self, params)
2570 response = api_call(self, params)
2553 self._compare_ok(random_id, True, given=response.body)
2571 self._compare_ok(random_id, True, given=response.body)
2554 pullrequest = PullRequest().get(pull_request_id)
2572 pullrequest = PullRequest().get(pull_request_id)
2555 assert pullrequest.comments[-1].text == ''
2573 assert pullrequest.comments[-1].text == ''
2556 assert pullrequest.status == PullRequest.STATUS_CLOSED
2574 assert pullrequest.status == PullRequest.STATUS_CLOSED
2557 assert pullrequest.is_closed() == True
2575 assert pullrequest.is_closed() == True
2558
2576
2559 def test_api_status_pullrequest(self):
2577 def test_api_status_pullrequest(self):
2560 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"status test")
2578 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"status test")
2561
2579
2562 random_id = random.randrange(1, 9999)
2580 random_id = random.randrange(1, 9999)
2563 params = json.dumps({
2581 params = json.dumps({
2564 "id": random_id,
2582 "id": random_id,
2565 "api_key": User.get_by_username(TEST_USER_REGULAR2_LOGIN).api_key,
2583 "api_key": User.get_by_username(TEST_USER_REGULAR2_LOGIN).api_key,
2566 "method": "comment_pullrequest",
2584 "method": "comment_pullrequest",
2567 "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
2585 "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
2568 })
2586 })
2569 response = api_call(self, params)
2587 response = api_call(self, params)
2570 pullrequest = PullRequest().get(pull_request_id)
2588 pullrequest = PullRequest().get(pull_request_id)
2571 self._compare_error(random_id, "No permission to change pull request status. User needs to be admin, owner or reviewer.", given=response.body)
2589 self._compare_error(random_id, "No permission to change pull request status. User needs to be admin, owner or reviewer.", given=response.body)
2572 assert ChangesetStatus.STATUS_UNDER_REVIEW == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
2590 assert ChangesetStatus.STATUS_UNDER_REVIEW == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
2573 params = json.dumps({
2591 params = json.dumps({
2574 "id": random_id,
2592 "id": random_id,
2575 "api_key": User.get_by_username(TEST_USER_REGULAR_LOGIN).api_key,
2593 "api_key": User.get_by_username(TEST_USER_REGULAR_LOGIN).api_key,
2576 "method": "comment_pullrequest",
2594 "method": "comment_pullrequest",
2577 "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
2595 "args": {"pull_request_id": pull_request_id, "status": ChangesetStatus.STATUS_APPROVED},
2578 })
2596 })
2579 response = api_call(self, params)
2597 response = api_call(self, params)
2580 self._compare_ok(random_id, True, given=response.body)
2598 self._compare_ok(random_id, True, given=response.body)
2581 pullrequest = PullRequest().get(pull_request_id)
2599 pullrequest = PullRequest().get(pull_request_id)
2582 assert ChangesetStatus.STATUS_APPROVED == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
2600 assert ChangesetStatus.STATUS_APPROVED == ChangesetStatusModel().calculate_pull_request_result(pullrequest)[2]
2583
2601
2584 def test_api_comment_pullrequest(self):
2602 def test_api_comment_pullrequest(self):
2585 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"comment test")
2603 pull_request_id = fixture.create_pullrequest(self, self.REPO, self.TEST_PR_SRC, self.TEST_PR_DST, u"comment test")
2586 random_id = random.randrange(1, 9999)
2604 random_id = random.randrange(1, 9999)
2587 params = json.dumps({
2605 params = json.dumps({
2588 "id": random_id,
2606 "id": random_id,
2589 "api_key": self.apikey,
2607 "api_key": self.apikey,
2590 "method": "comment_pullrequest",
2608 "method": "comment_pullrequest",
2591 "args": {"pull_request_id": pull_request_id, "comment_msg": "Looks good to me"},
2609 "args": {"pull_request_id": pull_request_id, "comment_msg": "Looks good to me"},
2592 })
2610 })
2593 response = api_call(self, params)
2611 response = api_call(self, params)
2594 self._compare_ok(random_id, True, given=response.body)
2612 self._compare_ok(random_id, True, given=response.body)
2595 pullrequest = PullRequest().get(pull_request_id)
2613 pullrequest = PullRequest().get(pull_request_id)
2596 assert pullrequest.comments[-1].text == u'Looks good to me'
2614 assert pullrequest.comments[-1].text == u'Looks good to me'
General Comments 0
You need to be logged in to leave comments. Login now