##// END OF EJS Templates
#404 API extensions for showing permission for users...
marcink -
r2151:12ceeda3 beta
parent child Browse files
Show More
@@ -1,650 +1,659 b''
1 .. _api:
1 .. _api:
2
2
3 ===
3 ===
4 API
4 API
5 ===
5 ===
6
6
7
7
8 Starting from RhodeCode version 1.2 a simple API was implemented.
8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 There's a single schema for calling all api methods. API is implemented
9 There's a single schema for calling all api methods. API is implemented
10 with JSON protocol both ways. An url to send API request in RhodeCode is
10 with JSON protocol both ways. An url to send API request in RhodeCode is
11 <your_server>/_admin/api
11 <your_server>/_admin/api
12
12
13 API ACCESS FOR WEB VIEWS
13 API ACCESS FOR WEB VIEWS
14 ++++++++++++++++++++++++
14 ++++++++++++++++++++++++
15
15
16 API access can also be turned on for each web view in RhodeCode that is
16 API access can also be turned on for each web view in RhodeCode that is
17 decorated with `@LoginRequired` decorator. To enable API access simple change
17 decorated with `@LoginRequired` decorator. To enable API access simple change
18 the standard login decorator to `@LoginRequired(api_access=True)`.
18 the standard login decorator to `@LoginRequired(api_access=True)`.
19 After this change, a rhodecode view can be accessed without login by adding a
19 After this change, a rhodecode view can be accessed without login by adding a
20 GET parameter `?api_key=<api_key>` to url. By default this is only
20 GET parameter `?api_key=<api_key>` to url. By default this is only
21 enabled on RSS/ATOM feed views.
21 enabled on RSS/ATOM feed views.
22
22
23
23
24 API ACCESS
24 API ACCESS
25 ++++++++++
25 ++++++++++
26
26
27 All clients are required to send JSON-RPC spec JSON data::
27 All clients are required to send JSON-RPC spec JSON data::
28
28
29 {
29 {
30 "id:"<id>",
30 "id:"<id>",
31 "api_key":"<api_key>",
31 "api_key":"<api_key>",
32 "method":"<method_name>",
32 "method":"<method_name>",
33 "args":{"<arg_key>":"<arg_val>"}
33 "args":{"<arg_key>":"<arg_val>"}
34 }
34 }
35
35
36 Example call for autopulling remotes repos using curl::
36 Example call for autopulling remotes repos using curl::
37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
38
38
39 Simply provide
39 Simply provide
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
41 - *api_key* for access and permission validation.
41 - *api_key* for access and permission validation.
42 - *method* is name of method to call
42 - *method* is name of method to call
43 - *args* is an key:value list of arguments to pass to method
43 - *args* is an key:value list of arguments to pass to method
44
44
45 .. note::
45 .. note::
46
46
47 api_key can be found in your user account page
47 api_key can be found in your user account page
48
48
49
49
50 RhodeCode API will return always a JSON-RPC response::
50 RhodeCode API will return always a JSON-RPC response::
51
51
52 {
52 {
53 "id":<id>, # matching id sent by request
53 "id":<id>, # matching id sent by request
54 "result": "<result>"|null, # JSON formatted result, null if any errors
54 "result": "<result>"|null, # JSON formatted result, null if any errors
55 "error": "null"|<error_message> # JSON formatted error (if any)
55 "error": "null"|<error_message> # JSON formatted error (if any)
56 }
56 }
57
57
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
59 calling api *error* key from response will contain failure description
59 calling api *error* key from response will contain failure description
60 and result will be null.
60 and result will be null.
61
61
62 API METHODS
62 API METHODS
63 +++++++++++
63 +++++++++++
64
64
65
65
66 pull
66 pull
67 ----
67 ----
68
68
69 Pulls given repo from remote location. Can be used to automatically keep
69 Pulls given repo from remote location. Can be used to automatically keep
70 remote repos up to date. This command can be executed only using api_key
70 remote repos up to date. This command can be executed only using api_key
71 belonging to user with admin rights
71 belonging to user with admin rights
72
72
73 INPUT::
73 INPUT::
74
74
75 id : <id_for_response>
75 id : <id_for_response>
76 api_key : "<api_key>"
76 api_key : "<api_key>"
77 method : "pull"
77 method : "pull"
78 args : {
78 args : {
79 "repo_name" : "<reponame>"
79 "repo_name" : "<reponame>"
80 }
80 }
81
81
82 OUTPUT::
82 OUTPUT::
83
83
84 result : "Pulled from <reponame>"
84 result : "Pulled from <reponame>"
85 error : null
85 error : null
86
86
87
87
88 get_user
88 get_user
89 --------
89 --------
90
90
91 Get's an user by username or user_id, Returns empty result if user is not found.
91 Get's an user by username or user_id, Returns empty result if user is not found.
92 This command can be executed only using api_key belonging to user with admin
92 This command can be executed only using api_key belonging to user with admin
93 rights.
93 rights.
94
94
95
95
96 INPUT::
96 INPUT::
97
97
98 id : <id_for_response>
98 id : <id_for_response>
99 api_key : "<api_key>"
99 api_key : "<api_key>"
100 method : "get_user"
100 method : "get_user"
101 args : {
101 args : {
102 "userid" : "<username or user_id>"
102 "userid" : "<username or user_id>"
103 }
103 }
104
104
105 OUTPUT::
105 OUTPUT::
106
106
107 result: None if user does not exist or
107 result: None if user does not exist or
108 {
108 {
109 "id" : "<id>",
109 "id" : "<id>",
110 "username" : "<username>",
110 "username" : "<username>",
111 "firstname": "<firstname>",
111 "firstname": "<firstname>",
112 "lastname" : "<lastname>",
112 "lastname" : "<lastname>",
113 "email" : "<email>",
113 "email" : "<email>",
114 "active" : "<bool>",
114 "active" : "<bool>",
115 "admin" :Β  "<bool>",
115 "admin" :Β  "<bool>",
116 "ldap_dn" : "<ldap_dn>"
116 "ldap_dn" : "<ldap_dn>",
117 "last_login": "<last_login>",
118 "permissions": {
119 "global": ["hg.create.repository",
120 "repository.read",
121 "hg.register.manual_activate"],
122 "repositories": {"repo1": "repository.none"},
123 "repositories_groups": {"Group1": "group.read"}
124 },
117 }
125 }
118
126
119 error: null
127 error: null
120
128
121
129
122 get_users
130 get_users
123 ---------
131 ---------
124
132
125 Lists all existing users. This command can be executed only using api_key
133 Lists all existing users. This command can be executed only using api_key
126 belonging to user with admin rights.
134 belonging to user with admin rights.
127
135
128
136
129 INPUT::
137 INPUT::
130
138
131 id : <id_for_response>
139 id : <id_for_response>
132 api_key : "<api_key>"
140 api_key : "<api_key>"
133 method : "get_users"
141 method : "get_users"
134 args : { }
142 args : { }
135
143
136 OUTPUT::
144 OUTPUT::
137
145
138 result: [
146 result: [
139 {
147 {
140 "id" : "<id>",
148 "id" : "<id>",
141 "username" : "<username>",
149 "username" : "<username>",
142 "firstname": "<firstname>",
150 "firstname": "<firstname>",
143 "lastname" : "<lastname>",
151 "lastname" : "<lastname>",
144 "email" : "<email>",
152 "email" : "<email>",
145 "active" : "<bool>",
153 "active" : "<bool>",
146 "admin" :Β  "<bool>",
154 "admin" :Β  "<bool>",
147 "ldap_dn" : "<ldap_dn>"
155 "ldap_dn" : "<ldap_dn>",
156 "last_login": "<last_login>",
148 },
157 },
149 …
158 …
150 ]
159 ]
151 error: null
160 error: null
152
161
153
162
154 create_user
163 create_user
155 -----------
164 -----------
156
165
157 Creates new user. This command can
166 Creates new user. This command can
158 be executed only using api_key belonging to user with admin rights.
167 be executed only using api_key belonging to user with admin rights.
159
168
160
169
161 INPUT::
170 INPUT::
162
171
163 id : <id_for_response>
172 id : <id_for_response>
164 api_key : "<api_key>"
173 api_key : "<api_key>"
165 method : "create_user"
174 method : "create_user"
166 args : {
175 args : {
167 "username" : "<username>",
176 "username" : "<username>",
168 "password" : "<password>",
177 "password" : "<password>",
169 "email" : "<useremail>",
178 "email" : "<useremail>",
170 "firstname" : "<firstname> = None",
179 "firstname" : "<firstname> = None",
171 "lastname" : "<lastname> = None",
180 "lastname" : "<lastname> = None",
172 "active" : "<bool> = True",
181 "active" : "<bool> = True",
173 "admin" : "<bool> = False",
182 "admin" : "<bool> = False",
174 "ldap_dn" : "<ldap_dn> = None"
183 "ldap_dn" : "<ldap_dn> = None"
175 }
184 }
176
185
177 OUTPUT::
186 OUTPUT::
178
187
179 result: {
188 result: {
180 "id" : "<new_user_id>",
189 "id" : "<new_user_id>",
181 "msg" : "created new user <username>"
190 "msg" : "created new user <username>"
182 }
191 }
183 error: null
192 error: null
184
193
185
194
186 update_user
195 update_user
187 -----------
196 -----------
188
197
189 updates current one if such user exists. This command can
198 updates current one if such user exists. This command can
190 be executed only using api_key belonging to user with admin rights.
199 be executed only using api_key belonging to user with admin rights.
191
200
192
201
193 INPUT::
202 INPUT::
194
203
195 id : <id_for_response>
204 id : <id_for_response>
196 api_key : "<api_key>"
205 api_key : "<api_key>"
197 method : "update_user"
206 method : "update_user"
198 args : {
207 args : {
199 "userid" : "<user_id or username>",
208 "userid" : "<user_id or username>",
200 "username" : "<username>",
209 "username" : "<username>",
201 "password" : "<password>",
210 "password" : "<password>",
202 "email" : "<useremail>",
211 "email" : "<useremail>",
203 "firstname" : "<firstname>",
212 "firstname" : "<firstname>",
204 "lastname" : "<lastname>",
213 "lastname" : "<lastname>",
205 "active" : "<bool>",
214 "active" : "<bool>",
206 "admin" : "<bool>",
215 "admin" : "<bool>",
207 "ldap_dn" : "<ldap_dn>"
216 "ldap_dn" : "<ldap_dn>"
208 }
217 }
209
218
210 OUTPUT::
219 OUTPUT::
211
220
212 result: {
221 result: {
213 "id" : "<edited_user_id>",
222 "id" : "<edited_user_id>",
214 "msg" : "updated user <username>"
223 "msg" : "updated user <username>"
215 }
224 }
216 error: null
225 error: null
217
226
218
227
219 get_users_group
228 get_users_group
220 ---------------
229 ---------------
221
230
222 Gets an existing users group. This command can be executed only using api_key
231 Gets an existing users group. This command can be executed only using api_key
223 belonging to user with admin rights.
232 belonging to user with admin rights.
224
233
225
234
226 INPUT::
235 INPUT::
227
236
228 id : <id_for_response>
237 id : <id_for_response>
229 api_key : "<api_key>"
238 api_key : "<api_key>"
230 method : "get_users_group"
239 method : "get_users_group"
231 args : {
240 args : {
232 "group_name" : "<name>"
241 "group_name" : "<name>"
233 }
242 }
234
243
235 OUTPUT::
244 OUTPUT::
236
245
237 result : None if group not exist
246 result : None if group not exist
238 {
247 {
239 "id" : "<id>",
248 "id" : "<id>",
240 "group_name" : "<groupname>",
249 "group_name" : "<groupname>",
241 "active": "<bool>",
250 "active": "<bool>",
242 "members" : [
251 "members" : [
243 { "id" : "<userid>",
252 { "id" : "<userid>",
244 "username" : "<username>",
253 "username" : "<username>",
245 "firstname": "<firstname>",
254 "firstname": "<firstname>",
246 "lastname" : "<lastname>",
255 "lastname" : "<lastname>",
247 "email" : "<email>",
256 "email" : "<email>",
248 "active" : "<bool>",
257 "active" : "<bool>",
249 "admin" :Β  "<bool>",
258 "admin" :Β  "<bool>",
250 "ldap" : "<ldap_dn>"
259 "ldap" : "<ldap_dn>"
251 },
260 },
252 …
261 …
253 ]
262 ]
254 }
263 }
255 error : null
264 error : null
256
265
257
266
258 get_users_groups
267 get_users_groups
259 ----------------
268 ----------------
260
269
261 Lists all existing users groups. This command can be executed only using
270 Lists all existing users groups. This command can be executed only using
262 api_key belonging to user with admin rights.
271 api_key belonging to user with admin rights.
263
272
264
273
265 INPUT::
274 INPUT::
266
275
267 id : <id_for_response>
276 id : <id_for_response>
268 api_key : "<api_key>"
277 api_key : "<api_key>"
269 method : "get_users_groups"
278 method : "get_users_groups"
270 args : { }
279 args : { }
271
280
272 OUTPUT::
281 OUTPUT::
273
282
274 result : [
283 result : [
275 {
284 {
276 "id" : "<id>",
285 "id" : "<id>",
277 "group_name" : "<groupname>",
286 "group_name" : "<groupname>",
278 "active": "<bool>",
287 "active": "<bool>",
279 "members" : [
288 "members" : [
280 {
289 {
281 "id" : "<userid>",
290 "id" : "<userid>",
282 "username" : "<username>",
291 "username" : "<username>",
283 "firstname": "<firstname>",
292 "firstname": "<firstname>",
284 "lastname" : "<lastname>",
293 "lastname" : "<lastname>",
285 "email" : "<email>",
294 "email" : "<email>",
286 "active" : "<bool>",
295 "active" : "<bool>",
287 "admin" :Β  "<bool>",
296 "admin" :Β  "<bool>",
288 "ldap" : "<ldap_dn>"
297 "ldap" : "<ldap_dn>"
289 },
298 },
290 …
299 …
291 ]
300 ]
292 }
301 }
293 ]
302 ]
294 error : null
303 error : null
295
304
296
305
297 create_users_group
306 create_users_group
298 ------------------
307 ------------------
299
308
300 Creates new users group. This command can be executed only using api_key
309 Creates new users group. This command can be executed only using api_key
301 belonging to user with admin rights
310 belonging to user with admin rights
302
311
303
312
304 INPUT::
313 INPUT::
305
314
306 id : <id_for_response>
315 id : <id_for_response>
307 api_key : "<api_key>"
316 api_key : "<api_key>"
308 method : "create_users_group"
317 method : "create_users_group"
309 args: {
318 args: {
310 "group_name": "<groupname>",
319 "group_name": "<groupname>",
311 "active":"<bool> = True"
320 "active":"<bool> = True"
312 }
321 }
313
322
314 OUTPUT::
323 OUTPUT::
315
324
316 result: {
325 result: {
317 "id": "<newusersgroupid>",
326 "id": "<newusersgroupid>",
318 "msg": "created new users group <groupname>"
327 "msg": "created new users group <groupname>"
319 }
328 }
320 error: null
329 error: null
321
330
322
331
323 add_user_to_users_group
332 add_user_to_users_group
324 -----------------------
333 -----------------------
325
334
326 Adds a user to a users group. If user exists in that group success will be
335 Adds a user to a users group. If user exists in that group success will be
327 `false`. This command can be executed only using api_key
336 `false`. This command can be executed only using api_key
328 belonging to user with admin rights
337 belonging to user with admin rights
329
338
330
339
331 INPUT::
340 INPUT::
332
341
333 id : <id_for_response>
342 id : <id_for_response>
334 api_key : "<api_key>"
343 api_key : "<api_key>"
335 method : "add_user_users_group"
344 method : "add_user_users_group"
336 args: {
345 args: {
337 "group_name" : "<groupname>",
346 "group_name" : "<groupname>",
338 "username" : "<username>"
347 "username" : "<username>"
339 }
348 }
340
349
341 OUTPUT::
350 OUTPUT::
342
351
343 result: {
352 result: {
344 "id": "<newusersgroupmemberid>",
353 "id": "<newusersgroupmemberid>",
345 "success": True|False # depends on if member is in group
354 "success": True|False # depends on if member is in group
346 "msg": "added member <username> to users group <groupname> |
355 "msg": "added member <username> to users group <groupname> |
347 User is already in that group"
356 User is already in that group"
348 }
357 }
349 error: null
358 error: null
350
359
351
360
352 remove_user_from_users_group
361 remove_user_from_users_group
353 ----------------------------
362 ----------------------------
354
363
355 Removes a user from a users group. If user is not in given group success will
364 Removes a user from a users group. If user is not in given group success will
356 be `false`. This command can be executed only
365 be `false`. This command can be executed only
357 using api_key belonging to user with admin rights
366 using api_key belonging to user with admin rights
358
367
359
368
360 INPUT::
369 INPUT::
361
370
362 id : <id_for_response>
371 id : <id_for_response>
363 api_key : "<api_key>"
372 api_key : "<api_key>"
364 method : "remove_user_from_users_group"
373 method : "remove_user_from_users_group"
365 args: {
374 args: {
366 "group_name" : "<groupname>",
375 "group_name" : "<groupname>",
367 "username" : "<username>"
376 "username" : "<username>"
368 }
377 }
369
378
370 OUTPUT::
379 OUTPUT::
371
380
372 result: {
381 result: {
373 "success": True|False, # depends on if member is in group
382 "success": True|False, # depends on if member is in group
374 "msg": "removed member <username> from users group <groupname> |
383 "msg": "removed member <username> from users group <groupname> |
375 User wasn't in group"
384 User wasn't in group"
376 }
385 }
377 error: null
386 error: null
378
387
379
388
380 get_repo
389 get_repo
381 --------
390 --------
382
391
383 Gets an existing repository by it's name or repository_id. Members will return
392 Gets an existing repository by it's name or repository_id. Members will return
384 either users_group or user associated to that repository. This command can
393 either users_group or user associated to that repository. This command can
385 be executed only using api_key belonging to user with admin rights.
394 be executed only using api_key belonging to user with admin rights.
386
395
387
396
388 INPUT::
397 INPUT::
389
398
390 id : <id_for_response>
399 id : <id_for_response>
391 api_key : "<api_key>"
400 api_key : "<api_key>"
392 method : "get_repo"
401 method : "get_repo"
393 args: {
402 args: {
394 "repoid" : "<reponame or repo_id>"
403 "repoid" : "<reponame or repo_id>"
395 }
404 }
396
405
397 OUTPUT::
406 OUTPUT::
398
407
399 result: None if repository does not exist or
408 result: None if repository does not exist or
400 {
409 {
401 "id" : "<id>",
410 "id" : "<id>",
402 "repo_name" : "<reponame>"
411 "repo_name" : "<reponame>"
403 "type" : "<type>",
412 "type" : "<type>",
404 "description" : "<description>",
413 "description" : "<description>",
405 "members" : [
414 "members" : [
406 {
415 {
407 "type": "user",
416 "type": "user",
408 "id" : "<userid>",
417 "id" : "<userid>",
409 "username" : "<username>",
418 "username" : "<username>",
410 "firstname": "<firstname>",
419 "firstname": "<firstname>",
411 "lastname" : "<lastname>",
420 "lastname" : "<lastname>",
412 "email" : "<email>",
421 "email" : "<email>",
413 "active" : "<bool>",
422 "active" : "<bool>",
414 "admin" :Β  "<bool>",
423 "admin" :Β  "<bool>",
415 "ldap" : "<ldap_dn>",
424 "ldap" : "<ldap_dn>",
416 "permission" : "repository.(read|write|admin)"
425 "permission" : "repository.(read|write|admin)"
417 },
426 },
418 …
427 …
419 {
428 {
420 "type": "users_group",
429 "type": "users_group",
421 "id" : "<usersgroupid>",
430 "id" : "<usersgroupid>",
422 "name" : "<usersgroupname>",
431 "name" : "<usersgroupname>",
423 "active": "<bool>",
432 "active": "<bool>",
424 "permission" : "repository.(read|write|admin)"
433 "permission" : "repository.(read|write|admin)"
425 },
434 },
426 …
435 …
427 ]
436 ]
428 }
437 }
429 error: null
438 error: null
430
439
431
440
432 get_repos
441 get_repos
433 ---------
442 ---------
434
443
435 Lists all existing repositories. This command can be executed only using api_key
444 Lists all existing repositories. This command can be executed only using api_key
436 belonging to user with admin rights
445 belonging to user with admin rights
437
446
438
447
439 INPUT::
448 INPUT::
440
449
441 id : <id_for_response>
450 id : <id_for_response>
442 api_key : "<api_key>"
451 api_key : "<api_key>"
443 method : "get_repos"
452 method : "get_repos"
444 args: { }
453 args: { }
445
454
446 OUTPUT::
455 OUTPUT::
447
456
448 result: [
457 result: [
449 {
458 {
450 "id" : "<id>",
459 "id" : "<id>",
451 "repo_name" : "<reponame>"
460 "repo_name" : "<reponame>"
452 "type" : "<type>",
461 "type" : "<type>",
453 "description" : "<description>"
462 "description" : "<description>"
454 },
463 },
455 …
464 …
456 ]
465 ]
457 error: null
466 error: null
458
467
459
468
460 get_repo_nodes
469 get_repo_nodes
461 --------------
470 --------------
462
471
463 returns a list of nodes and it's children in a flat list for a given path
472 returns a list of nodes and it's children in a flat list for a given path
464 at given revision. It's possible to specify ret_type to show only `files` or
473 at given revision. It's possible to specify ret_type to show only `files` or
465 `dirs`. This command can be executed only using api_key belonging to user
474 `dirs`. This command can be executed only using api_key belonging to user
466 with admin rights
475 with admin rights
467
476
468
477
469 INPUT::
478 INPUT::
470
479
471 id : <id_for_response>
480 id : <id_for_response>
472 api_key : "<api_key>"
481 api_key : "<api_key>"
473 method : "get_repo_nodes"
482 method : "get_repo_nodes"
474 args: {
483 args: {
475 "repo_name" : "<reponame>",
484 "repo_name" : "<reponame>",
476 "revision" : "<revision>",
485 "revision" : "<revision>",
477 "root_path" : "<root_path>",
486 "root_path" : "<root_path>",
478 "ret_type" : "<ret_type>" = 'all'
487 "ret_type" : "<ret_type>" = 'all'
479 }
488 }
480
489
481 OUTPUT::
490 OUTPUT::
482
491
483 result: [
492 result: [
484 {
493 {
485 "name" : "<name>"
494 "name" : "<name>"
486 "type" : "<type>",
495 "type" : "<type>",
487 },
496 },
488 …
497 …
489 ]
498 ]
490 error: null
499 error: null
491
500
492
501
493 create_repo
502 create_repo
494 -----------
503 -----------
495
504
496 Creates a repository. This command can be executed only using api_key
505 Creates a repository. This command can be executed only using api_key
497 belonging to user with admin rights.
506 belonging to user with admin rights.
498 If repository name contains "/", all needed repository groups will be created.
507 If repository name contains "/", all needed repository groups will be created.
499 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
508 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
500 and create "baz" repository with "bar" as group.
509 and create "baz" repository with "bar" as group.
501
510
502
511
503 INPUT::
512 INPUT::
504
513
505 id : <id_for_response>
514 id : <id_for_response>
506 api_key : "<api_key>"
515 api_key : "<api_key>"
507 method : "create_repo"
516 method : "create_repo"
508 args: {
517 args: {
509 "repo_name" : "<reponame>",
518 "repo_name" : "<reponame>",
510 "owner_name" : "<ownername>",
519 "owner_name" : "<ownername>",
511 "description" : "<description> = ''",
520 "description" : "<description> = ''",
512 "repo_type" : "<type> = 'hg'",
521 "repo_type" : "<type> = 'hg'",
513 "private" : "<bool> = False",
522 "private" : "<bool> = False",
514 "clone_uri" : "<clone_uri> = None",
523 "clone_uri" : "<clone_uri> = None",
515 }
524 }
516
525
517 OUTPUT::
526 OUTPUT::
518
527
519 result: {
528 result: {
520 "id": "<newrepoid>",
529 "id": "<newrepoid>",
521 "msg": "Created new repository <reponame>",
530 "msg": "Created new repository <reponame>",
522 }
531 }
523 error: null
532 error: null
524
533
525
534
526 delete_repo
535 delete_repo
527 -----------
536 -----------
528
537
529 Deletes a repository. This command can be executed only using api_key
538 Deletes a repository. This command can be executed only using api_key
530 belonging to user with admin rights.
539 belonging to user with admin rights.
531
540
532
541
533 INPUT::
542 INPUT::
534
543
535 id : <id_for_response>
544 id : <id_for_response>
536 api_key : "<api_key>"
545 api_key : "<api_key>"
537 method : "delete_repo"
546 method : "delete_repo"
538 args: {
547 args: {
539 "repo_name" : "<reponame>",
548 "repo_name" : "<reponame>",
540 }
549 }
541
550
542 OUTPUT::
551 OUTPUT::
543
552
544 result: {
553 result: {
545 "msg": "Deleted repository <reponame>",
554 "msg": "Deleted repository <reponame>",
546 }
555 }
547 error: null
556 error: null
548
557
549
558
550 grant_user_permission
559 grant_user_permission
551 ---------------------
560 ---------------------
552
561
553 Grant permission for user on given repository, or update existing one
562 Grant permission for user on given repository, or update existing one
554 if found. This command can be executed only using api_key belonging to user
563 if found. This command can be executed only using api_key belonging to user
555 with admin rights.
564 with admin rights.
556
565
557
566
558 INPUT::
567 INPUT::
559
568
560 id : <id_for_response>
569 id : <id_for_response>
561 api_key : "<api_key>"
570 api_key : "<api_key>"
562 method : "grant_user_permission"
571 method : "grant_user_permission"
563 args: {
572 args: {
564 "repo_name" : "<reponame>",
573 "repo_name" : "<reponame>",
565 "username" : "<username>",
574 "username" : "<username>",
566 "perm" : "(repository.(none|read|write|admin))",
575 "perm" : "(repository.(none|read|write|admin))",
567 }
576 }
568
577
569 OUTPUT::
578 OUTPUT::
570
579
571 result: {
580 result: {
572 "msg" : "Granted perm: <perm> for user: <username> in repo: <reponame>"
581 "msg" : "Granted perm: <perm> for user: <username> in repo: <reponame>"
573 }
582 }
574 error: null
583 error: null
575
584
576
585
577 revoke_user_permission
586 revoke_user_permission
578 ----------------------
587 ----------------------
579
588
580 Revoke permission for user on given repository. This command can be executed
589 Revoke permission for user on given repository. This command can be executed
581 only using api_key belonging to user with admin rights.
590 only using api_key belonging to user with admin rights.
582
591
583
592
584 INPUT::
593 INPUT::
585
594
586 id : <id_for_response>
595 id : <id_for_response>
587 api_key : "<api_key>"
596 api_key : "<api_key>"
588 method : "revoke_user_permission"
597 method : "revoke_user_permission"
589 args: {
598 args: {
590 "repo_name" : "<reponame>",
599 "repo_name" : "<reponame>",
591 "username" : "<username>",
600 "username" : "<username>",
592 }
601 }
593
602
594 OUTPUT::
603 OUTPUT::
595
604
596 result: {
605 result: {
597 "msg" : "Revoked perm for user: <suername> in repo: <reponame>"
606 "msg" : "Revoked perm for user: <suername> in repo: <reponame>"
598 }
607 }
599 error: null
608 error: null
600
609
601
610
602 grant_users_group_permission
611 grant_users_group_permission
603 ----------------------------
612 ----------------------------
604
613
605 Grant permission for users group on given repository, or update
614 Grant permission for users group on given repository, or update
606 existing one if found. This command can be executed only using
615 existing one if found. This command can be executed only using
607 api_key belonging to user with admin rights.
616 api_key belonging to user with admin rights.
608
617
609
618
610 INPUT::
619 INPUT::
611
620
612 id : <id_for_response>
621 id : <id_for_response>
613 api_key : "<api_key>"
622 api_key : "<api_key>"
614 method : "grant_users_group_permission"
623 method : "grant_users_group_permission"
615 args: {
624 args: {
616 "repo_name" : "<reponame>",
625 "repo_name" : "<reponame>",
617 "group_name" : "<usersgroupname>",
626 "group_name" : "<usersgroupname>",
618 "perm" : "(repository.(none|read|write|admin))",
627 "perm" : "(repository.(none|read|write|admin))",
619 }
628 }
620
629
621 OUTPUT::
630 OUTPUT::
622
631
623 result: {
632 result: {
624 "msg" : "Granted perm: <perm> for group: <usersgroupname> in repo: <reponame>"
633 "msg" : "Granted perm: <perm> for group: <usersgroupname> in repo: <reponame>"
625 }
634 }
626 error: null
635 error: null
627
636
628
637
629 revoke_users_group_permission
638 revoke_users_group_permission
630 -----------------------------
639 -----------------------------
631
640
632 Revoke permission for users group on given repository.This command can be
641 Revoke permission for users group on given repository.This command can be
633 executed only using api_key belonging to user with admin rights.
642 executed only using api_key belonging to user with admin rights.
634
643
635 INPUT::
644 INPUT::
636
645
637 id : <id_for_response>
646 id : <id_for_response>
638 api_key : "<api_key>"
647 api_key : "<api_key>"
639 method : "revoke_users_group_permission"
648 method : "revoke_users_group_permission"
640 args: {
649 args: {
641 "repo_name" : "<reponame>",
650 "repo_name" : "<reponame>",
642 "users_group" : "<usersgroupname>",
651 "users_group" : "<usersgroupname>",
643 }
652 }
644
653
645 OUTPUT::
654 OUTPUT::
646
655
647 result: {
656 result: {
648 "msg" : "Revoked perm for group: <usersgroupname> in repo: <reponame>"
657 "msg" : "Revoked perm for group: <usersgroupname> in repo: <reponame>"
649 }
658 }
650 error: null No newline at end of file
659 error: null
@@ -1,600 +1,602 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7
7
8 1.3.4 (**2012-XX-XX**)
8 1.3.4 (**2012-XX-XX**)
9 ----------------------
9 ----------------------
10
10
11 :status: in-progress
11 :status: in-progress
12 :branch: beta
12 :branch: beta
13
13
14 news
14 news
15 ++++
15 ++++
16
16
17 - Whoosh logging is now controlled by the .ini files logging setup
17 - Whoosh logging is now controlled by the .ini files logging setup
18 - added clone-url into edit form on /settings page
18 - added clone-url into edit form on /settings page
19 - added help text into repo add/edit forms
19 - added help text into repo add/edit forms
20 - created rcextensions module with additional mappings (ref #322) and
20 - created rcextensions module with additional mappings (ref #322) and
21 post push/pull/create repo hooks callbacks
21 post push/pull/create repo hooks callbacks
22 - implemented #377 Users view for his own permissions on account page
22 - implemented #377 Users view for his own permissions on account page
23 - #399 added inheritance of permissions for users group on repos groups
23 - #399 added inheritance of permissions for users group on repos groups
24 - #401 repository group is automatically pre-selected when adding repos
24 - #401 repository group is automatically pre-selected when adding repos
25 inside a repository group
25 inside a repository group
26 - added alternative HTTP 403 response when client failed to authenticate. Helps
26 - added alternative HTTP 403 response when client failed to authenticate. Helps
27 solving issues with Mercurial and LDAP
27 solving issues with Mercurial and LDAP
28 - #402 removed group prefix from repository name when listing repositories
28 - #402 removed group prefix from repository name when listing repositories
29 inside a group
29 inside a group
30 - added gravatars into permission view and permissions autocomplete
30 - added gravatars into permission view and permissions autocomplete
31 - #347 when running multiple RhodeCode instances, properly invalidates cache
31 - #347 when running multiple RhodeCode instances, properly invalidates cache
32 for all registered servers
32 for all registered servers
33
33
34 fixes
34 fixes
35 +++++
35 +++++
36
36
37 - fixed #390 cache invalidation problems on repos inside group
37 - fixed #390 cache invalidation problems on repos inside group
38 - fixed #385 clone by ID url was loosing proxy prefix in URL
38 - fixed #385 clone by ID url was loosing proxy prefix in URL
39 - fixed some unicode problems with waitress
39 - fixed some unicode problems with waitress
40 - fixed issue with escaping < and > in changeset commits
40 - fixed issue with escaping < and > in changeset commits
41 - fixed error occurring during recursive group creation in API
41 - fixed error occurring during recursive group creation in API
42 create_repo function
42 create_repo function
43 - fixed #393 py2.5 fixes for routes url generator
43 - fixed #393 py2.5 fixes for routes url generator
44 - fixed #397 Private repository groups shows up before login
44 - fixed #397 Private repository groups shows up before login
45 - fixed #396 fixed problems with revoking users in nested groups
45 - fixed #396 fixed problems with revoking users in nested groups
46 - fixed mysql unicode issues + specified InnoDB as default engine with
47 utf8 charset
46
48
47 1.3.3 (**2012-03-02**)
49 1.3.3 (**2012-03-02**)
48 ----------------------
50 ----------------------
49
51
50 news
52 news
51 ++++
53 ++++
52
54
53
55
54 fixes
56 fixes
55 +++++
57 +++++
56
58
57 - fixed some python2.5 compatibility issues
59 - fixed some python2.5 compatibility issues
58 - fixed issues with removed repos was accidentally added as groups, after
60 - fixed issues with removed repos was accidentally added as groups, after
59 full rescan of paths
61 full rescan of paths
60 - fixes #376 Cannot edit user (using container auth)
62 - fixes #376 Cannot edit user (using container auth)
61 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
63 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
62 configuration
64 configuration
63 - fixed initial sorting of repos inside repo group
65 - fixed initial sorting of repos inside repo group
64 - fixes issue when user tried to resubmit same permission into user/user_groups
66 - fixes issue when user tried to resubmit same permission into user/user_groups
65 - bumped beaker version that fixes #375 leap error bug
67 - bumped beaker version that fixes #375 leap error bug
66 - fixed raw_changeset for git. It was generated with hg patch headers
68 - fixed raw_changeset for git. It was generated with hg patch headers
67 - fixed vcs issue with last_changeset for filenodes
69 - fixed vcs issue with last_changeset for filenodes
68 - fixed missing commit after hook delete
70 - fixed missing commit after hook delete
69 - fixed #372 issues with git operation detection that caused a security issue
71 - fixed #372 issues with git operation detection that caused a security issue
70 for git repos
72 for git repos
71
73
72 1.3.2 (**2012-02-28**)
74 1.3.2 (**2012-02-28**)
73 ----------------------
75 ----------------------
74
76
75 news
77 news
76 ++++
78 ++++
77
79
78
80
79 fixes
81 fixes
80 +++++
82 +++++
81
83
82 - fixed git protocol issues with repos-groups
84 - fixed git protocol issues with repos-groups
83 - fixed git remote repos validator that prevented from cloning remote git repos
85 - fixed git remote repos validator that prevented from cloning remote git repos
84 - fixes #370 ending slashes fixes for repo and groups
86 - fixes #370 ending slashes fixes for repo and groups
85 - fixes #368 improved git-protocol detection to handle other clients
87 - fixes #368 improved git-protocol detection to handle other clients
86 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
88 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
87 Moved To Root
89 Moved To Root
88 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
90 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
89 - fixed #373 missing cascade drop on user_group_to_perm table
91 - fixed #373 missing cascade drop on user_group_to_perm table
90
92
91 1.3.1 (**2012-02-27**)
93 1.3.1 (**2012-02-27**)
92 ----------------------
94 ----------------------
93
95
94 news
96 news
95 ++++
97 ++++
96
98
97
99
98 fixes
100 fixes
99 +++++
101 +++++
100
102
101 - redirection loop occurs when remember-me wasn't checked during login
103 - redirection loop occurs when remember-me wasn't checked during login
102 - fixes issues with git blob history generation
104 - fixes issues with git blob history generation
103 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
105 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
104
106
105 1.3.0 (**2012-02-26**)
107 1.3.0 (**2012-02-26**)
106 ----------------------
108 ----------------------
107
109
108 news
110 news
109 ++++
111 ++++
110
112
111 - code review, inspired by github code-comments
113 - code review, inspired by github code-comments
112 - #215 rst and markdown README files support
114 - #215 rst and markdown README files support
113 - #252 Container-based and proxy pass-through authentication support
115 - #252 Container-based and proxy pass-through authentication support
114 - #44 branch browser. Filtering of changelog by branches
116 - #44 branch browser. Filtering of changelog by branches
115 - mercurial bookmarks support
117 - mercurial bookmarks support
116 - new hover top menu, optimized to add maximum size for important views
118 - new hover top menu, optimized to add maximum size for important views
117 - configurable clone url template with possibility to specify protocol like
119 - configurable clone url template with possibility to specify protocol like
118 ssh:// or http:// and also manually alter other parts of clone_url.
120 ssh:// or http:// and also manually alter other parts of clone_url.
119 - enabled largefiles extension by default
121 - enabled largefiles extension by default
120 - optimized summary file pages and saved a lot of unused space in them
122 - optimized summary file pages and saved a lot of unused space in them
121 - #239 option to manually mark repository as fork
123 - #239 option to manually mark repository as fork
122 - #320 mapping of commit authors to RhodeCode users
124 - #320 mapping of commit authors to RhodeCode users
123 - #304 hashes are displayed using monospace font
125 - #304 hashes are displayed using monospace font
124 - diff configuration, toggle white lines and context lines
126 - diff configuration, toggle white lines and context lines
125 - #307 configurable diffs, whitespace toggle, increasing context lines
127 - #307 configurable diffs, whitespace toggle, increasing context lines
126 - sorting on branches, tags and bookmarks using YUI datatable
128 - sorting on branches, tags and bookmarks using YUI datatable
127 - improved file filter on files page
129 - improved file filter on files page
128 - implements #330 api method for listing nodes ar particular revision
130 - implements #330 api method for listing nodes ar particular revision
129 - #73 added linking issues in commit messages to chosen issue tracker url
131 - #73 added linking issues in commit messages to chosen issue tracker url
130 based on user defined regular expression
132 based on user defined regular expression
131 - added linking of changesets in commit messages
133 - added linking of changesets in commit messages
132 - new compact changelog with expandable commit messages
134 - new compact changelog with expandable commit messages
133 - firstname and lastname are optional in user creation
135 - firstname and lastname are optional in user creation
134 - #348 added post-create repository hook
136 - #348 added post-create repository hook
135 - #212 global encoding settings is now configurable from .ini files
137 - #212 global encoding settings is now configurable from .ini files
136 - #227 added repository groups permissions
138 - #227 added repository groups permissions
137 - markdown gets codehilite extensions
139 - markdown gets codehilite extensions
138 - new API methods, delete_repositories, grante/revoke permissions for groups
140 - new API methods, delete_repositories, grante/revoke permissions for groups
139 and repos
141 and repos
140
142
141
143
142 fixes
144 fixes
143 +++++
145 +++++
144
146
145 - rewrote dbsession management for atomic operations, and better error handling
147 - rewrote dbsession management for atomic operations, and better error handling
146 - fixed sorting of repo tables
148 - fixed sorting of repo tables
147 - #326 escape of special html entities in diffs
149 - #326 escape of special html entities in diffs
148 - normalized user_name => username in api attributes
150 - normalized user_name => username in api attributes
149 - fixes #298 ldap created users with mixed case emails created conflicts
151 - fixes #298 ldap created users with mixed case emails created conflicts
150 on saving a form
152 on saving a form
151 - fixes issue when owner of a repo couldn't revoke permissions for users
153 - fixes issue when owner of a repo couldn't revoke permissions for users
152 and groups
154 and groups
153 - fixes #271 rare JSON serialization problem with statistics
155 - fixes #271 rare JSON serialization problem with statistics
154 - fixes #337 missing validation check for conflicting names of a group with a
156 - fixes #337 missing validation check for conflicting names of a group with a
155 repositories group
157 repositories group
156 - #340 fixed session problem for mysql and celery tasks
158 - #340 fixed session problem for mysql and celery tasks
157 - fixed #331 RhodeCode mangles repository names if the a repository group
159 - fixed #331 RhodeCode mangles repository names if the a repository group
158 contains the "full path" to the repositories
160 contains the "full path" to the repositories
159 - #355 RhodeCode doesn't store encrypted LDAP passwords
161 - #355 RhodeCode doesn't store encrypted LDAP passwords
160
162
161 1.2.5 (**2012-01-28**)
163 1.2.5 (**2012-01-28**)
162 ----------------------
164 ----------------------
163
165
164 news
166 news
165 ++++
167 ++++
166
168
167 fixes
169 fixes
168 +++++
170 +++++
169
171
170 - #340 Celery complains about MySQL server gone away, added session cleanup
172 - #340 Celery complains about MySQL server gone away, added session cleanup
171 for celery tasks
173 for celery tasks
172 - #341 "scanning for repositories in None" log message during Rescan was missing
174 - #341 "scanning for repositories in None" log message during Rescan was missing
173 a parameter
175 a parameter
174 - fixed creating archives with subrepos. Some hooks were triggered during that
176 - fixed creating archives with subrepos. Some hooks were triggered during that
175 operation leading to crash.
177 operation leading to crash.
176 - fixed missing email in account page.
178 - fixed missing email in account page.
177 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
179 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
178 forking on windows impossible
180 forking on windows impossible
179
181
180 1.2.4 (**2012-01-19**)
182 1.2.4 (**2012-01-19**)
181 ----------------------
183 ----------------------
182
184
183 news
185 news
184 ++++
186 ++++
185
187
186 - RhodeCode is bundled with mercurial series 2.0.X by default, with
188 - RhodeCode is bundled with mercurial series 2.0.X by default, with
187 full support to largefiles extension. Enabled by default in new installations
189 full support to largefiles extension. Enabled by default in new installations
188 - #329 Ability to Add/Remove Groups to/from a Repository via AP
190 - #329 Ability to Add/Remove Groups to/from a Repository via AP
189 - added requires.txt file with requirements
191 - added requires.txt file with requirements
190
192
191 fixes
193 fixes
192 +++++
194 +++++
193
195
194 - fixes db session issues with celery when emailing admins
196 - fixes db session issues with celery when emailing admins
195 - #331 RhodeCode mangles repository names if the a repository group
197 - #331 RhodeCode mangles repository names if the a repository group
196 contains the "full path" to the repositories
198 contains the "full path" to the repositories
197 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
199 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
198 - DB session cleanup after hg protocol operations, fixes issues with
200 - DB session cleanup after hg protocol operations, fixes issues with
199 `mysql has gone away` errors
201 `mysql has gone away` errors
200 - #333 doc fixes for get_repo api function
202 - #333 doc fixes for get_repo api function
201 - #271 rare JSON serialization problem with statistics enabled
203 - #271 rare JSON serialization problem with statistics enabled
202 - #337 Fixes issues with validation of repository name conflicting with
204 - #337 Fixes issues with validation of repository name conflicting with
203 a group name. A proper message is now displayed.
205 a group name. A proper message is now displayed.
204 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
206 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
205 doesn't work
207 doesn't work
206 - #316 fixes issues with web description in hgrc files
208 - #316 fixes issues with web description in hgrc files
207
209
208 1.2.3 (**2011-11-02**)
210 1.2.3 (**2011-11-02**)
209 ----------------------
211 ----------------------
210
212
211 news
213 news
212 ++++
214 ++++
213
215
214 - added option to manage repos group for non admin users
216 - added option to manage repos group for non admin users
215 - added following API methods for get_users, create_user, get_users_groups,
217 - added following API methods for get_users, create_user, get_users_groups,
216 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
218 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
217 get_repo, create_repo, add_user_to_repo
219 get_repo, create_repo, add_user_to_repo
218 - implements #237 added password confirmation for my account
220 - implements #237 added password confirmation for my account
219 and admin edit user.
221 and admin edit user.
220 - implements #291 email notification for global events are now sent to all
222 - implements #291 email notification for global events are now sent to all
221 administrator users, and global config email.
223 administrator users, and global config email.
222
224
223 fixes
225 fixes
224 +++++
226 +++++
225
227
226 - added option for passing auth method for smtp mailer
228 - added option for passing auth method for smtp mailer
227 - #276 issue with adding a single user with id>10 to usergroups
229 - #276 issue with adding a single user with id>10 to usergroups
228 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
230 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
229 - #288 fixes managing of repos in a group for non admin user
231 - #288 fixes managing of repos in a group for non admin user
230
232
231 1.2.2 (**2011-10-17**)
233 1.2.2 (**2011-10-17**)
232 ----------------------
234 ----------------------
233
235
234 news
236 news
235 ++++
237 ++++
236
238
237 - #226 repo groups are available by path instead of numerical id
239 - #226 repo groups are available by path instead of numerical id
238
240
239 fixes
241 fixes
240 +++++
242 +++++
241
243
242 - #259 Groups with the same name but with different parent group
244 - #259 Groups with the same name but with different parent group
243 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
245 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
244 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
246 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
245 - #265 ldap save fails sometimes on converting attributes to booleans,
247 - #265 ldap save fails sometimes on converting attributes to booleans,
246 added getter and setter into model that will prevent from this on db model level
248 added getter and setter into model that will prevent from this on db model level
247 - fixed problems with timestamps issues #251 and #213
249 - fixed problems with timestamps issues #251 and #213
248 - fixes #266 RhodeCode allows to create repo with the same name and in
250 - fixes #266 RhodeCode allows to create repo with the same name and in
249 the same parent as group
251 the same parent as group
250 - fixes #245 Rescan of the repositories on Windows
252 - fixes #245 Rescan of the repositories on Windows
251 - fixes #248 cannot edit repos inside a group on windows
253 - fixes #248 cannot edit repos inside a group on windows
252 - fixes #219 forking problems on windows
254 - fixes #219 forking problems on windows
253
255
254 1.2.1 (**2011-10-08**)
256 1.2.1 (**2011-10-08**)
255 ----------------------
257 ----------------------
256
258
257 news
259 news
258 ++++
260 ++++
259
261
260
262
261 fixes
263 fixes
262 +++++
264 +++++
263
265
264 - fixed problems with basic auth and push problems
266 - fixed problems with basic auth and push problems
265 - gui fixes
267 - gui fixes
266 - fixed logger
268 - fixed logger
267
269
268 1.2.0 (**2011-10-07**)
270 1.2.0 (**2011-10-07**)
269 ----------------------
271 ----------------------
270
272
271 news
273 news
272 ++++
274 ++++
273
275
274 - implemented #47 repository groups
276 - implemented #47 repository groups
275 - implemented #89 Can setup google analytics code from settings menu
277 - implemented #89 Can setup google analytics code from settings menu
276 - implemented #91 added nicer looking archive urls with more download options
278 - implemented #91 added nicer looking archive urls with more download options
277 like tags, branches
279 like tags, branches
278 - implemented #44 into file browsing, and added follow branch option
280 - implemented #44 into file browsing, and added follow branch option
279 - implemented #84 downloads can be enabled/disabled for each repository
281 - implemented #84 downloads can be enabled/disabled for each repository
280 - anonymous repository can be cloned without having to pass default:default
282 - anonymous repository can be cloned without having to pass default:default
281 into clone url
283 into clone url
282 - fixed #90 whoosh indexer can index chooses repositories passed in command
284 - fixed #90 whoosh indexer can index chooses repositories passed in command
283 line
285 line
284 - extended journal with day aggregates and paging
286 - extended journal with day aggregates and paging
285 - implemented #107 source code lines highlight ranges
287 - implemented #107 source code lines highlight ranges
286 - implemented #93 customizable changelog on combined revision ranges -
288 - implemented #93 customizable changelog on combined revision ranges -
287 equivalent of githubs compare view
289 equivalent of githubs compare view
288 - implemented #108 extended and more powerful LDAP configuration
290 - implemented #108 extended and more powerful LDAP configuration
289 - implemented #56 users groups
291 - implemented #56 users groups
290 - major code rewrites optimized codes for speed and memory usage
292 - major code rewrites optimized codes for speed and memory usage
291 - raw and diff downloads are now in git format
293 - raw and diff downloads are now in git format
292 - setup command checks for write access to given path
294 - setup command checks for write access to given path
293 - fixed many issues with international characters and unicode. It uses utf8
295 - fixed many issues with international characters and unicode. It uses utf8
294 decode with replace to provide less errors even with non utf8 encoded strings
296 decode with replace to provide less errors even with non utf8 encoded strings
295 - #125 added API KEY access to feeds
297 - #125 added API KEY access to feeds
296 - #109 Repository can be created from external Mercurial link (aka. remote
298 - #109 Repository can be created from external Mercurial link (aka. remote
297 repository, and manually updated (via pull) from admin panel
299 repository, and manually updated (via pull) from admin panel
298 - beta git support - push/pull server + basic view for git repos
300 - beta git support - push/pull server + basic view for git repos
299 - added followers page and forks page
301 - added followers page and forks page
300 - server side file creation (with binary file upload interface)
302 - server side file creation (with binary file upload interface)
301 and edition with commits powered by codemirror
303 and edition with commits powered by codemirror
302 - #111 file browser file finder, quick lookup files on whole file tree
304 - #111 file browser file finder, quick lookup files on whole file tree
303 - added quick login sliding menu into main page
305 - added quick login sliding menu into main page
304 - changelog uses lazy loading of affected files details, in some scenarios
306 - changelog uses lazy loading of affected files details, in some scenarios
305 this can improve speed of changelog page dramatically especially for
307 this can improve speed of changelog page dramatically especially for
306 larger repositories.
308 larger repositories.
307 - implements #214 added support for downloading subrepos in download menu.
309 - implements #214 added support for downloading subrepos in download menu.
308 - Added basic API for direct operations on rhodecode via JSON
310 - Added basic API for direct operations on rhodecode via JSON
309 - Implemented advanced hook management
311 - Implemented advanced hook management
310
312
311 fixes
313 fixes
312 +++++
314 +++++
313
315
314 - fixed file browser bug, when switching into given form revision the url was
316 - fixed file browser bug, when switching into given form revision the url was
315 not changing
317 not changing
316 - fixed propagation to error controller on simplehg and simplegit middlewares
318 - fixed propagation to error controller on simplehg and simplegit middlewares
317 - fixed error when trying to make a download on empty repository
319 - fixed error when trying to make a download on empty repository
318 - fixed problem with '[' chars in commit messages in journal
320 - fixed problem with '[' chars in commit messages in journal
319 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
321 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
320 - journal fork fixes
322 - journal fork fixes
321 - removed issue with space inside renamed repository after deletion
323 - removed issue with space inside renamed repository after deletion
322 - fixed strange issue on formencode imports
324 - fixed strange issue on formencode imports
323 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
325 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
324 - #150 fixes for errors on repositories mapped in db but corrupted in
326 - #150 fixes for errors on repositories mapped in db but corrupted in
325 filesystem
327 filesystem
326 - fixed problem with ascendant characters in realm #181
328 - fixed problem with ascendant characters in realm #181
327 - fixed problem with sqlite file based database connection pool
329 - fixed problem with sqlite file based database connection pool
328 - whoosh indexer and code stats share the same dynamic extensions map
330 - whoosh indexer and code stats share the same dynamic extensions map
329 - fixes #188 - relationship delete of repo_to_perm entry on user removal
331 - fixes #188 - relationship delete of repo_to_perm entry on user removal
330 - fixes issue #189 Trending source files shows "show more" when no more exist
332 - fixes issue #189 Trending source files shows "show more" when no more exist
331 - fixes issue #197 Relative paths for pidlocks
333 - fixes issue #197 Relative paths for pidlocks
332 - fixes issue #198 password will require only 3 chars now for login form
334 - fixes issue #198 password will require only 3 chars now for login form
333 - fixes issue #199 wrong redirection for non admin users after creating a repository
335 - fixes issue #199 wrong redirection for non admin users after creating a repository
334 - fixes issues #202, bad db constraint made impossible to attach same group
336 - fixes issues #202, bad db constraint made impossible to attach same group
335 more than one time. Affects only mysql/postgres
337 more than one time. Affects only mysql/postgres
336 - fixes #218 os.kill patch for windows was missing sig param
338 - fixes #218 os.kill patch for windows was missing sig param
337 - improved rendering of dag (they are not trimmed anymore when number of
339 - improved rendering of dag (they are not trimmed anymore when number of
338 heads exceeds 5)
340 heads exceeds 5)
339
341
340 1.1.8 (**2011-04-12**)
342 1.1.8 (**2011-04-12**)
341 ----------------------
343 ----------------------
342
344
343 news
345 news
344 ++++
346 ++++
345
347
346 - improved windows support
348 - improved windows support
347
349
348 fixes
350 fixes
349 +++++
351 +++++
350
352
351 - fixed #140 freeze of python dateutil library, since new version is python2.x
353 - fixed #140 freeze of python dateutil library, since new version is python2.x
352 incompatible
354 incompatible
353 - setup-app will check for write permission in given path
355 - setup-app will check for write permission in given path
354 - cleaned up license info issue #149
356 - cleaned up license info issue #149
355 - fixes for issues #137,#116 and problems with unicode and accented characters.
357 - fixes for issues #137,#116 and problems with unicode and accented characters.
356 - fixes crashes on gravatar, when passed in email as unicode
358 - fixes crashes on gravatar, when passed in email as unicode
357 - fixed tooltip flickering problems
359 - fixed tooltip flickering problems
358 - fixed came_from redirection on windows
360 - fixed came_from redirection on windows
359 - fixed logging modules, and sql formatters
361 - fixed logging modules, and sql formatters
360 - windows fixes for os.kill issue #133
362 - windows fixes for os.kill issue #133
361 - fixes path splitting for windows issues #148
363 - fixes path splitting for windows issues #148
362 - fixed issue #143 wrong import on migration to 1.1.X
364 - fixed issue #143 wrong import on migration to 1.1.X
363 - fixed problems with displaying binary files, thanks to Thomas Waldmann
365 - fixed problems with displaying binary files, thanks to Thomas Waldmann
364 - removed name from archive files since it's breaking ui for long repo names
366 - removed name from archive files since it's breaking ui for long repo names
365 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
367 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
366 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
368 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
367 Thomas Waldmann
369 Thomas Waldmann
368 - fixed issue #166 summary pager was skipping 10 revisions on second page
370 - fixed issue #166 summary pager was skipping 10 revisions on second page
369
371
370
372
371 1.1.7 (**2011-03-23**)
373 1.1.7 (**2011-03-23**)
372 ----------------------
374 ----------------------
373
375
374 news
376 news
375 ++++
377 ++++
376
378
377 fixes
379 fixes
378 +++++
380 +++++
379
381
380 - fixed (again) #136 installation support for FreeBSD
382 - fixed (again) #136 installation support for FreeBSD
381
383
382
384
383 1.1.6 (**2011-03-21**)
385 1.1.6 (**2011-03-21**)
384 ----------------------
386 ----------------------
385
387
386 news
388 news
387 ++++
389 ++++
388
390
389 fixes
391 fixes
390 +++++
392 +++++
391
393
392 - fixed #136 installation support for FreeBSD
394 - fixed #136 installation support for FreeBSD
393 - RhodeCode will check for python version during installation
395 - RhodeCode will check for python version during installation
394
396
395 1.1.5 (**2011-03-17**)
397 1.1.5 (**2011-03-17**)
396 ----------------------
398 ----------------------
397
399
398 news
400 news
399 ++++
401 ++++
400
402
401 - basic windows support, by exchanging pybcrypt into sha256 for windows only
403 - basic windows support, by exchanging pybcrypt into sha256 for windows only
402 highly inspired by idea of mantis406
404 highly inspired by idea of mantis406
403
405
404 fixes
406 fixes
405 +++++
407 +++++
406
408
407 - fixed sorting by author in main page
409 - fixed sorting by author in main page
408 - fixed crashes with diffs on binary files
410 - fixed crashes with diffs on binary files
409 - fixed #131 problem with boolean values for LDAP
411 - fixed #131 problem with boolean values for LDAP
410 - fixed #122 mysql problems thanks to striker69
412 - fixed #122 mysql problems thanks to striker69
411 - fixed problem with errors on calling raw/raw_files/annotate functions
413 - fixed problem with errors on calling raw/raw_files/annotate functions
412 with unknown revisions
414 with unknown revisions
413 - fixed returned rawfiles attachment names with international character
415 - fixed returned rawfiles attachment names with international character
414 - cleaned out docs, big thanks to Jason Harris
416 - cleaned out docs, big thanks to Jason Harris
415
417
416 1.1.4 (**2011-02-19**)
418 1.1.4 (**2011-02-19**)
417 ----------------------
419 ----------------------
418
420
419 news
421 news
420 ++++
422 ++++
421
423
422 fixes
424 fixes
423 +++++
425 +++++
424
426
425 - fixed formencode import problem on settings page, that caused server crash
427 - fixed formencode import problem on settings page, that caused server crash
426 when that page was accessed as first after server start
428 when that page was accessed as first after server start
427 - journal fixes
429 - journal fixes
428 - fixed option to access repository just by entering http://server/<repo_name>
430 - fixed option to access repository just by entering http://server/<repo_name>
429
431
430 1.1.3 (**2011-02-16**)
432 1.1.3 (**2011-02-16**)
431 ----------------------
433 ----------------------
432
434
433 news
435 news
434 ++++
436 ++++
435
437
436 - implemented #102 allowing the '.' character in username
438 - implemented #102 allowing the '.' character in username
437 - added option to access repository just by entering http://server/<repo_name>
439 - added option to access repository just by entering http://server/<repo_name>
438 - celery task ignores result for better performance
440 - celery task ignores result for better performance
439
441
440 fixes
442 fixes
441 +++++
443 +++++
442
444
443 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
445 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
444 apollo13 and Johan Walles
446 apollo13 and Johan Walles
445 - small fixes in journal
447 - small fixes in journal
446 - fixed problems with getting setting for celery from .ini files
448 - fixed problems with getting setting for celery from .ini files
447 - registration, password reset and login boxes share the same title as main
449 - registration, password reset and login boxes share the same title as main
448 application now
450 application now
449 - fixed #113: to high permissions to fork repository
451 - fixed #113: to high permissions to fork repository
450 - fixed problem with '[' chars in commit messages in journal
452 - fixed problem with '[' chars in commit messages in journal
451 - removed issue with space inside renamed repository after deletion
453 - removed issue with space inside renamed repository after deletion
452 - db transaction fixes when filesystem repository creation failed
454 - db transaction fixes when filesystem repository creation failed
453 - fixed #106 relation issues on databases different than sqlite
455 - fixed #106 relation issues on databases different than sqlite
454 - fixed static files paths links to use of url() method
456 - fixed static files paths links to use of url() method
455
457
456 1.1.2 (**2011-01-12**)
458 1.1.2 (**2011-01-12**)
457 ----------------------
459 ----------------------
458
460
459 news
461 news
460 ++++
462 ++++
461
463
462
464
463 fixes
465 fixes
464 +++++
466 +++++
465
467
466 - fixes #98 protection against float division of percentage stats
468 - fixes #98 protection against float division of percentage stats
467 - fixed graph bug
469 - fixed graph bug
468 - forced webhelpers version since it was making troubles during installation
470 - forced webhelpers version since it was making troubles during installation
469
471
470 1.1.1 (**2011-01-06**)
472 1.1.1 (**2011-01-06**)
471 ----------------------
473 ----------------------
472
474
473 news
475 news
474 ++++
476 ++++
475
477
476 - added force https option into ini files for easier https usage (no need to
478 - added force https option into ini files for easier https usage (no need to
477 set server headers with this options)
479 set server headers with this options)
478 - small css updates
480 - small css updates
479
481
480 fixes
482 fixes
481 +++++
483 +++++
482
484
483 - fixed #96 redirect loop on files view on repositories without changesets
485 - fixed #96 redirect loop on files view on repositories without changesets
484 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
486 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
485 and server crashed with errors
487 and server crashed with errors
486 - fixed large tooltips problems on main page
488 - fixed large tooltips problems on main page
487 - fixed #92 whoosh indexer is more error proof
489 - fixed #92 whoosh indexer is more error proof
488
490
489 1.1.0 (**2010-12-18**)
491 1.1.0 (**2010-12-18**)
490 ----------------------
492 ----------------------
491
493
492 news
494 news
493 ++++
495 ++++
494
496
495 - rewrite of internals for vcs >=0.1.10
497 - rewrite of internals for vcs >=0.1.10
496 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
498 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
497 with older clients
499 with older clients
498 - anonymous access, authentication via ldap
500 - anonymous access, authentication via ldap
499 - performance upgrade for cached repos list - each repository has its own
501 - performance upgrade for cached repos list - each repository has its own
500 cache that's invalidated when needed.
502 cache that's invalidated when needed.
501 - performance upgrades on repositories with large amount of commits (20K+)
503 - performance upgrades on repositories with large amount of commits (20K+)
502 - main page quick filter for filtering repositories
504 - main page quick filter for filtering repositories
503 - user dashboards with ability to follow chosen repositories actions
505 - user dashboards with ability to follow chosen repositories actions
504 - sends email to admin on new user registration
506 - sends email to admin on new user registration
505 - added cache/statistics reset options into repository settings
507 - added cache/statistics reset options into repository settings
506 - more detailed action logger (based on hooks) with pushed changesets lists
508 - more detailed action logger (based on hooks) with pushed changesets lists
507 and options to disable those hooks from admin panel
509 and options to disable those hooks from admin panel
508 - introduced new enhanced changelog for merges that shows more accurate results
510 - introduced new enhanced changelog for merges that shows more accurate results
509 - new improved and faster code stats (based on pygments lexers mapping tables,
511 - new improved and faster code stats (based on pygments lexers mapping tables,
510 showing up to 10 trending sources for each repository. Additionally stats
512 showing up to 10 trending sources for each repository. Additionally stats
511 can be disabled in repository settings.
513 can be disabled in repository settings.
512 - gui optimizations, fixed application width to 1024px
514 - gui optimizations, fixed application width to 1024px
513 - added cut off (for large files/changesets) limit into config files
515 - added cut off (for large files/changesets) limit into config files
514 - whoosh, celeryd, upgrade moved to paster command
516 - whoosh, celeryd, upgrade moved to paster command
515 - other than sqlite database backends can be used
517 - other than sqlite database backends can be used
516
518
517 fixes
519 fixes
518 +++++
520 +++++
519
521
520 - fixes #61 forked repo was showing only after cache expired
522 - fixes #61 forked repo was showing only after cache expired
521 - fixes #76 no confirmation on user deletes
523 - fixes #76 no confirmation on user deletes
522 - fixes #66 Name field misspelled
524 - fixes #66 Name field misspelled
523 - fixes #72 block user removal when he owns repositories
525 - fixes #72 block user removal when he owns repositories
524 - fixes #69 added password confirmation fields
526 - fixes #69 added password confirmation fields
525 - fixes #87 RhodeCode crashes occasionally on updating repository owner
527 - fixes #87 RhodeCode crashes occasionally on updating repository owner
526 - fixes #82 broken annotations on files with more than 1 blank line at the end
528 - fixes #82 broken annotations on files with more than 1 blank line at the end
527 - a lot of fixes and tweaks for file browser
529 - a lot of fixes and tweaks for file browser
528 - fixed detached session issues
530 - fixed detached session issues
529 - fixed when user had no repos he would see all repos listed in my account
531 - fixed when user had no repos he would see all repos listed in my account
530 - fixed ui() instance bug when global hgrc settings was loaded for server
532 - fixed ui() instance bug when global hgrc settings was loaded for server
531 instance and all hgrc options were merged with our db ui() object
533 instance and all hgrc options were merged with our db ui() object
532 - numerous small bugfixes
534 - numerous small bugfixes
533
535
534 (special thanks for TkSoh for detailed feedback)
536 (special thanks for TkSoh for detailed feedback)
535
537
536
538
537 1.0.2 (**2010-11-12**)
539 1.0.2 (**2010-11-12**)
538 ----------------------
540 ----------------------
539
541
540 news
542 news
541 ++++
543 ++++
542
544
543 - tested under python2.7
545 - tested under python2.7
544 - bumped sqlalchemy and celery versions
546 - bumped sqlalchemy and celery versions
545
547
546 fixes
548 fixes
547 +++++
549 +++++
548
550
549 - fixed #59 missing graph.js
551 - fixed #59 missing graph.js
550 - fixed repo_size crash when repository had broken symlinks
552 - fixed repo_size crash when repository had broken symlinks
551 - fixed python2.5 crashes.
553 - fixed python2.5 crashes.
552
554
553
555
554 1.0.1 (**2010-11-10**)
556 1.0.1 (**2010-11-10**)
555 ----------------------
557 ----------------------
556
558
557 news
559 news
558 ++++
560 ++++
559
561
560 - small css updated
562 - small css updated
561
563
562 fixes
564 fixes
563 +++++
565 +++++
564
566
565 - fixed #53 python2.5 incompatible enumerate calls
567 - fixed #53 python2.5 incompatible enumerate calls
566 - fixed #52 disable mercurial extension for web
568 - fixed #52 disable mercurial extension for web
567 - fixed #51 deleting repositories don't delete it's dependent objects
569 - fixed #51 deleting repositories don't delete it's dependent objects
568
570
569
571
570 1.0.0 (**2010-11-02**)
572 1.0.0 (**2010-11-02**)
571 ----------------------
573 ----------------------
572
574
573 - security bugfix simplehg wasn't checking for permissions on commands
575 - security bugfix simplehg wasn't checking for permissions on commands
574 other than pull or push.
576 other than pull or push.
575 - fixed doubled messages after push or pull in admin journal
577 - fixed doubled messages after push or pull in admin journal
576 - templating and css corrections, fixed repo switcher on chrome, updated titles
578 - templating and css corrections, fixed repo switcher on chrome, updated titles
577 - admin menu accessible from options menu on repository view
579 - admin menu accessible from options menu on repository view
578 - permissions cached queries
580 - permissions cached queries
579
581
580 1.0.0rc4 (**2010-10-12**)
582 1.0.0rc4 (**2010-10-12**)
581 --------------------------
583 --------------------------
582
584
583 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
585 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
584 - removed cache_manager settings from sqlalchemy meta
586 - removed cache_manager settings from sqlalchemy meta
585 - added sqlalchemy cache settings to ini files
587 - added sqlalchemy cache settings to ini files
586 - validated password length and added second try of failure on paster setup-app
588 - validated password length and added second try of failure on paster setup-app
587 - fixed setup database destroy prompt even when there was no db
589 - fixed setup database destroy prompt even when there was no db
588
590
589
591
590 1.0.0rc3 (**2010-10-11**)
592 1.0.0rc3 (**2010-10-11**)
591 -------------------------
593 -------------------------
592
594
593 - fixed i18n during installation.
595 - fixed i18n during installation.
594
596
595 1.0.0rc2 (**2010-10-11**)
597 1.0.0rc2 (**2010-10-11**)
596 -------------------------
598 -------------------------
597
599
598 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
600 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
599 occure. After vcs is fixed it'll be put back again.
601 occure. After vcs is fixed it'll be put back again.
600 - templating/css rewrites, optimized css. No newline at end of file
602 - templating/css rewrites, optimized css.
@@ -1,262 +1,262 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.api
3 rhodecode.controllers.api
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 JSON RPC controller
6 JSON RPC controller
7
7
8 :created_on: Aug 20, 2011
8 :created_on: Aug 20, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import inspect
28 import inspect
29 import logging
29 import logging
30 import types
30 import types
31 import urllib
31 import urllib
32 import traceback
32 import traceback
33
33
34 from rhodecode.lib.compat import izip_longest, json
34 from rhodecode.lib.compat import izip_longest, json
35
35
36 from paste.response import replace_header
36 from paste.response import replace_header
37
37
38 from pylons.controllers import WSGIController
38 from pylons.controllers import WSGIController
39
39
40
40
41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
42 HTTPBadRequest, HTTPError
42 HTTPBadRequest, HTTPError
43
43
44 from rhodecode.model.db import User
44 from rhodecode.model.db import User
45 from rhodecode.lib.auth import AuthUser
45 from rhodecode.lib.auth import AuthUser
46
46
47 log = logging.getLogger('JSONRPC')
47 log = logging.getLogger('JSONRPC')
48
48
49
49
50 class JSONRPCError(BaseException):
50 class JSONRPCError(BaseException):
51
51
52 def __init__(self, message):
52 def __init__(self, message):
53 self.message = message
53 self.message = message
54 super(JSONRPCError, self).__init__()
54 super(JSONRPCError, self).__init__()
55
55
56 def __str__(self):
56 def __str__(self):
57 return str(self.message)
57 return str(self.message)
58
58
59
59
60 def jsonrpc_error(message, code=None):
60 def jsonrpc_error(message, code=None):
61 """
61 """
62 Generate a Response object with a JSON-RPC error body
62 Generate a Response object with a JSON-RPC error body
63 """
63 """
64 from pylons.controllers.util import Response
64 from pylons.controllers.util import Response
65 resp = Response(body=json.dumps(dict(id=None, result=None, error=message)),
65 resp = Response(body=json.dumps(dict(id=None, result=None, error=message)),
66 status=code,
66 status=code,
67 content_type='application/json')
67 content_type='application/json')
68 return resp
68 return resp
69
69
70
70
71 class JSONRPCController(WSGIController):
71 class JSONRPCController(WSGIController):
72 """
72 """
73 A WSGI-speaking JSON-RPC controller class
73 A WSGI-speaking JSON-RPC controller class
74
74
75 See the specification:
75 See the specification:
76 <http://json-rpc.org/wiki/specification>`.
76 <http://json-rpc.org/wiki/specification>`.
77
77
78 Valid controller return values should be json-serializable objects.
78 Valid controller return values should be json-serializable objects.
79
79
80 Sub-classes should catch their exceptions and raise JSONRPCError
80 Sub-classes should catch their exceptions and raise JSONRPCError
81 if they want to pass meaningful errors to the client.
81 if they want to pass meaningful errors to the client.
82
82
83 """
83 """
84
84
85 def _get_method_args(self):
85 def _get_method_args(self):
86 """
86 """
87 Return `self._rpc_args` to dispatched controller method
87 Return `self._rpc_args` to dispatched controller method
88 chosen by __call__
88 chosen by __call__
89 """
89 """
90 return self._rpc_args
90 return self._rpc_args
91
91
92 def __call__(self, environ, start_response):
92 def __call__(self, environ, start_response):
93 """
93 """
94 Parse the request body as JSON, look up the method on the
94 Parse the request body as JSON, look up the method on the
95 controller and if it exists, dispatch to it.
95 controller and if it exists, dispatch to it.
96 """
96 """
97 if 'CONTENT_LENGTH' not in environ:
97 if 'CONTENT_LENGTH' not in environ:
98 log.debug("No Content-Length")
98 log.debug("No Content-Length")
99 return jsonrpc_error(message="No Content-Length in request")
99 return jsonrpc_error(message="No Content-Length in request")
100 else:
100 else:
101 length = environ['CONTENT_LENGTH'] or 0
101 length = environ['CONTENT_LENGTH'] or 0
102 length = int(environ['CONTENT_LENGTH'])
102 length = int(environ['CONTENT_LENGTH'])
103 log.debug('Content-Length: %s' % length)
103 log.debug('Content-Length: %s' % length)
104
104
105 if length == 0:
105 if length == 0:
106 log.debug("Content-Length is 0")
106 log.debug("Content-Length is 0")
107 return jsonrpc_error(message="Content-Length is 0")
107 return jsonrpc_error(message="Content-Length is 0")
108
108
109 raw_body = environ['wsgi.input'].read(length)
109 raw_body = environ['wsgi.input'].read(length)
110
110
111 try:
111 try:
112 json_body = json.loads(urllib.unquote_plus(raw_body))
112 json_body = json.loads(urllib.unquote_plus(raw_body))
113 except ValueError, e:
113 except ValueError, e:
114 # catch JSON errors Here
114 # catch JSON errors Here
115 return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
115 return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
116 % (e, urllib.unquote_plus(raw_body)))
116 % (e, urllib.unquote_plus(raw_body)))
117
117
118 # check AUTH based on API KEY
118 # check AUTH based on API KEY
119 try:
119 try:
120 self._req_api_key = json_body['api_key']
120 self._req_api_key = json_body['api_key']
121 self._req_id = json_body['id']
121 self._req_id = json_body['id']
122 self._req_method = json_body['method']
122 self._req_method = json_body['method']
123 self._request_params = json_body['args']
123 self._request_params = json_body['args']
124 log.debug(
124 log.debug(
125 'method: %s, params: %s' % (self._req_method,
125 'method: %s, params: %s' % (self._req_method,
126 self._request_params)
126 self._request_params)
127 )
127 )
128 except KeyError, e:
128 except KeyError, e:
129 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
129 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
130
130
131 # check if we can find this session using api_key
131 # check if we can find this session using api_key
132 try:
132 try:
133 u = User.get_by_api_key(self._req_api_key)
133 u = User.get_by_api_key(self._req_api_key)
134 if u is None:
134 if u is None:
135 return jsonrpc_error(message='Invalid API KEY')
135 return jsonrpc_error(message='Invalid API KEY')
136 auth_u = AuthUser(u.user_id, self._req_api_key)
136 auth_u = AuthUser(u.user_id, self._req_api_key)
137 except Exception, e:
137 except Exception, e:
138 return jsonrpc_error(message='Invalid API KEY')
138 return jsonrpc_error(message='Invalid API KEY')
139
139
140 self._error = None
140 self._error = None
141 try:
141 try:
142 self._func = self._find_method()
142 self._func = self._find_method()
143 except AttributeError, e:
143 except AttributeError, e:
144 return jsonrpc_error(message=str(e))
144 return jsonrpc_error(message=str(e))
145
145
146 # now that we have a method, add self._req_params to
146 # now that we have a method, add self._req_params to
147 # self.kargs and dispatch control to WGIController
147 # self.kargs and dispatch control to WGIController
148 argspec = inspect.getargspec(self._func)
148 argspec = inspect.getargspec(self._func)
149 arglist = argspec[0][1:]
149 arglist = argspec[0][1:]
150 defaults = map(type, argspec[3] or [])
150 defaults = map(type, argspec[3] or [])
151 default_empty = types.NotImplementedType
151 default_empty = types.NotImplementedType
152
152
153 # kw arguments required by this method
153 # kw arguments required by this method
154 func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
154 func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
155 fillvalue=default_empty))
155 fillvalue=default_empty))
156
156
157 # this is little trick to inject logged in user for
157 # this is little trick to inject logged in user for
158 # perms decorators to work they expect the controller class to have
158 # perms decorators to work they expect the controller class to have
159 # rhodecode_user attribute set
159 # rhodecode_user attribute set
160 self.rhodecode_user = auth_u
160 self.rhodecode_user = auth_u
161
161
162 # This attribute will need to be first param of a method that uses
162 # This attribute will need to be first param of a method that uses
163 # api_key, which is translated to instance of user at that name
163 # api_key, which is translated to instance of user at that name
164 USER_SESSION_ATTR = 'apiuser'
164 USER_SESSION_ATTR = 'apiuser'
165
165
166 if USER_SESSION_ATTR not in arglist:
166 if USER_SESSION_ATTR not in arglist:
167 return jsonrpc_error(message='This method [%s] does not support '
167 return jsonrpc_error(message='This method [%s] does not support '
168 'authentication (missing %s param)' %
168 'authentication (missing %s param)' %
169 (self._func.__name__, USER_SESSION_ATTR))
169 (self._func.__name__, USER_SESSION_ATTR))
170
170
171 # get our arglist and check if we provided them as args
171 # get our arglist and check if we provided them as args
172 for arg, default in func_kwargs.iteritems():
172 for arg, default in func_kwargs.iteritems():
173 if arg == USER_SESSION_ATTR:
173 if arg == USER_SESSION_ATTR:
174 # USER_SESSION_ATTR is something translated from api key and
174 # USER_SESSION_ATTR is something translated from api key and
175 # this is checked before so we don't need validate it
175 # this is checked before so we don't need validate it
176 continue
176 continue
177
177
178 # skip the required param check if it's default value is
178 # skip the required param check if it's default value is
179 # NotImplementedType (default_empty)
179 # NotImplementedType (default_empty)
180 if (default == default_empty and arg not in self._request_params):
180 if (default == default_empty and arg not in self._request_params):
181 return jsonrpc_error(
181 return jsonrpc_error(
182 message=(
182 message=(
183 'Missing non optional `%s` arg in JSON DATA' % arg
183 'Missing non optional `%s` arg in JSON DATA' % arg
184 )
184 )
185 )
185 )
186
186
187 self._rpc_args = {USER_SESSION_ATTR: u}
187 self._rpc_args = {USER_SESSION_ATTR: u}
188 self._rpc_args.update(self._request_params)
188 self._rpc_args.update(self._request_params)
189
189
190 self._rpc_args['action'] = self._req_method
190 self._rpc_args['action'] = self._req_method
191 self._rpc_args['environ'] = environ
191 self._rpc_args['environ'] = environ
192 self._rpc_args['start_response'] = start_response
192 self._rpc_args['start_response'] = start_response
193
193
194 status = []
194 status = []
195 headers = []
195 headers = []
196 exc_info = []
196 exc_info = []
197
197
198 def change_content(new_status, new_headers, new_exc_info=None):
198 def change_content(new_status, new_headers, new_exc_info=None):
199 status.append(new_status)
199 status.append(new_status)
200 headers.extend(new_headers)
200 headers.extend(new_headers)
201 exc_info.append(new_exc_info)
201 exc_info.append(new_exc_info)
202
202
203 output = WSGIController.__call__(self, environ, change_content)
203 output = WSGIController.__call__(self, environ, change_content)
204 output = list(output)
204 output = list(output)
205 headers.append(('Content-Length', str(len(output[0]))))
205 headers.append(('Content-Length', str(len(output[0]))))
206 replace_header(headers, 'Content-Type', 'application/json')
206 replace_header(headers, 'Content-Type', 'application/json')
207 start_response(status[0], headers, exc_info[0])
207 start_response(status[0], headers, exc_info[0])
208
208
209 return output
209 return output
210
210
211 def _dispatch_call(self):
211 def _dispatch_call(self):
212 """
212 """
213 Implement dispatch interface specified by WSGIController
213 Implement dispatch interface specified by WSGIController
214 """
214 """
215 try:
215 try:
216 raw_response = self._inspect_call(self._func)
216 raw_response = self._inspect_call(self._func)
217 if isinstance(raw_response, HTTPError):
217 if isinstance(raw_response, HTTPError):
218 self._error = str(raw_response)
218 self._error = str(raw_response)
219 except JSONRPCError, e:
219 except JSONRPCError, e:
220 self._error = str(e)
220 self._error = str(e)
221 except Exception, e:
221 except Exception, e:
222 log.error('Encountered unhandled exception: %s' \
222 log.error('Encountered unhandled exception: %s' \
223 % traceback.format_exc())
223 % traceback.format_exc())
224 json_exc = JSONRPCError('Internal server error')
224 json_exc = JSONRPCError('Internal server error')
225 self._error = str(json_exc)
225 self._error = str(json_exc)
226
226
227 if self._error is not None:
227 if self._error is not None:
228 raw_response = None
228 raw_response = None
229
229
230 response = dict(id=self._req_id, result=raw_response,
230 response = dict(id=self._req_id, result=raw_response,
231 error=self._error)
231 error=self._error)
232
232
233 try:
233 try:
234 return json.dumps(response)
234 return json.dumps(response)
235 except TypeError, e:
235 except TypeError, e:
236 log.debug('Error encoding response: %s' % e)
236 log.error('API FAILED. Error encoding response: %s' % e)
237 return json.dumps(
237 return json.dumps(
238 dict(
238 dict(
239 self._req_id,
239 id=self._req_id,
240 result=None,
240 result=None,
241 error="Error encoding response"
241 error="Error encoding response"
242 )
242 )
243 )
243 )
244
244
245 def _find_method(self):
245 def _find_method(self):
246 """
246 """
247 Return method named by `self._req_method` in controller if able
247 Return method named by `self._req_method` in controller if able
248 """
248 """
249 log.debug('Trying to find JSON-RPC method: %s' % self._req_method)
249 log.debug('Trying to find JSON-RPC method: %s' % self._req_method)
250 if self._req_method.startswith('_'):
250 if self._req_method.startswith('_'):
251 raise AttributeError("Method not allowed")
251 raise AttributeError("Method not allowed")
252
252
253 try:
253 try:
254 func = getattr(self, self._req_method, None)
254 func = getattr(self, self._req_method, None)
255 except UnicodeEncodeError:
255 except UnicodeEncodeError:
256 raise AttributeError("Problem decoding unicode in requested "
256 raise AttributeError("Problem decoding unicode in requested "
257 "method name.")
257 "method name.")
258
258
259 if isinstance(func, types.MethodType):
259 if isinstance(func, types.MethodType):
260 return func
260 return func
261 else:
261 else:
262 raise AttributeError("No such method: %s" % self._req_method)
262 raise AttributeError("No such method: %s" % self._req_method)
@@ -1,656 +1,657 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.api
3 rhodecode.controllers.api
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 API controller for RhodeCode
6 API controller for RhodeCode
7
7
8 :created_on: Aug 20, 2011
8 :created_on: Aug 20, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import traceback
28 import traceback
29 import logging
29 import logging
30
30
31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
31 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
32 from rhodecode.lib.auth import HasPermissionAllDecorator, \
33 HasPermissionAnyDecorator, PasswordGenerator
33 HasPermissionAnyDecorator, PasswordGenerator, AuthUser
34
34
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 from rhodecode.model.scm import ScmModel
36 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
37 from rhodecode.model.db import User, UsersGroup, Repository
38 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.user import UserModel
39 from rhodecode.model.user import UserModel
40 from rhodecode.model.users_group import UsersGroupModel
40 from rhodecode.model.users_group import UsersGroupModel
41 from rhodecode.model.repos_group import ReposGroupModel
42 from rhodecode.lib.utils import map_groups
41 from rhodecode.lib.utils import map_groups
43
42
44
45 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
46
44
47
45
48 class ApiController(JSONRPCController):
46 class ApiController(JSONRPCController):
49 """
47 """
50 API Controller
48 API Controller
51
49
52
50
53 Each method needs to have USER as argument this is then based on given
51 Each method needs to have USER as argument this is then based on given
54 API_KEY propagated as instance of user object
52 API_KEY propagated as instance of user object
55
53
56 Preferably this should be first argument also
54 Preferably this should be first argument also
57
55
58
56
59 Each function should also **raise** JSONRPCError for any
57 Each function should also **raise** JSONRPCError for any
60 errors that happens
58 errors that happens
61
59
62 """
60 """
63
61
64 @HasPermissionAllDecorator('hg.admin')
62 @HasPermissionAllDecorator('hg.admin')
65 def pull(self, apiuser, repo_name):
63 def pull(self, apiuser, repo_name):
66 """
64 """
67 Dispatch pull action on given repo
65 Dispatch pull action on given repo
68
66
69
67
70 :param user:
68 :param user:
71 :param repo_name:
69 :param repo_name:
72 """
70 """
73
71
74 if Repository.is_valid(repo_name) is False:
72 if Repository.is_valid(repo_name) is False:
75 raise JSONRPCError('Unknown repo "%s"' % repo_name)
73 raise JSONRPCError('Unknown repo "%s"' % repo_name)
76
74
77 try:
75 try:
78 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
76 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
79 return 'Pulled from %s' % repo_name
77 return 'Pulled from %s' % repo_name
80 except Exception:
78 except Exception:
81 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
79 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
82
80
83 @HasPermissionAllDecorator('hg.admin')
81 @HasPermissionAllDecorator('hg.admin')
84 def get_user(self, apiuser, userid):
82 def get_user(self, apiuser, userid):
85 """"
83 """"
86 Get a user by username
84 Get a user by username
87
85
88 :param apiuser:
86 :param apiuser:
89 :param username:
87 :param username:
90 """
88 """
91
89
92 user = UserModel().get_user(userid)
90 user = UserModel().get_user(userid)
93 if user is None:
91 if user is None:
94 return user
92 return user
95
93
96 return dict(
94 return dict(
97 id=user.user_id,
95 id=user.user_id,
98 username=user.username,
96 username=user.username,
99 firstname=user.name,
97 firstname=user.name,
100 lastname=user.lastname,
98 lastname=user.lastname,
101 email=user.email,
99 email=user.email,
102 active=user.active,
100 active=user.active,
103 admin=user.admin,
101 admin=user.admin,
104 ldap_dn=user.ldap_dn
102 ldap_dn=user.ldap_dn,
103 last_login=user.last_login,
104 permissions=AuthUser(user_id=user.user_id).permissions
105 )
105 )
106
106
107 @HasPermissionAllDecorator('hg.admin')
107 @HasPermissionAllDecorator('hg.admin')
108 def get_users(self, apiuser):
108 def get_users(self, apiuser):
109 """"
109 """"
110 Get all users
110 Get all users
111
111
112 :param apiuser:
112 :param apiuser:
113 """
113 """
114
114
115 result = []
115 result = []
116 for user in User.getAll():
116 for user in User.getAll():
117 result.append(
117 result.append(
118 dict(
118 dict(
119 id=user.user_id,
119 id=user.user_id,
120 username=user.username,
120 username=user.username,
121 firstname=user.name,
121 firstname=user.name,
122 lastname=user.lastname,
122 lastname=user.lastname,
123 email=user.email,
123 email=user.email,
124 active=user.active,
124 active=user.active,
125 admin=user.admin,
125 admin=user.admin,
126 ldap_dn=user.ldap_dn
126 ldap_dn=user.ldap_dn,
127 last_login=user.last_login,
127 )
128 )
128 )
129 )
129 return result
130 return result
130
131
131 @HasPermissionAllDecorator('hg.admin')
132 @HasPermissionAllDecorator('hg.admin')
132 def create_user(self, apiuser, username, email, password, firstname=None,
133 def create_user(self, apiuser, username, email, password, firstname=None,
133 lastname=None, active=True, admin=False, ldap_dn=None):
134 lastname=None, active=True, admin=False, ldap_dn=None):
134 """
135 """
135 Create new user
136 Create new user
136
137
137 :param apiuser:
138 :param apiuser:
138 :param username:
139 :param username:
139 :param password:
140 :param password:
140 :param email:
141 :param email:
141 :param name:
142 :param name:
142 :param lastname:
143 :param lastname:
143 :param active:
144 :param active:
144 :param admin:
145 :param admin:
145 :param ldap_dn:
146 :param ldap_dn:
146 """
147 """
147 if User.get_by_username(username):
148 if User.get_by_username(username):
148 raise JSONRPCError("user %s already exist" % username)
149 raise JSONRPCError("user %s already exist" % username)
149
150
150 if User.get_by_email(email, case_insensitive=True):
151 if User.get_by_email(email, case_insensitive=True):
151 raise JSONRPCError("email %s already exist" % email)
152 raise JSONRPCError("email %s already exist" % email)
152
153
153 if ldap_dn:
154 if ldap_dn:
154 # generate temporary password if ldap_dn
155 # generate temporary password if ldap_dn
155 password = PasswordGenerator().gen_password(length=8)
156 password = PasswordGenerator().gen_password(length=8)
156
157
157 try:
158 try:
158 usr = UserModel().create_or_update(
159 usr = UserModel().create_or_update(
159 username, password, email, firstname,
160 username, password, email, firstname,
160 lastname, active, admin, ldap_dn
161 lastname, active, admin, ldap_dn
161 )
162 )
162 Session.commit()
163 Session.commit()
163 return dict(
164 return dict(
164 id=usr.user_id,
165 id=usr.user_id,
165 msg='created new user %s' % username
166 msg='created new user %s' % username
166 )
167 )
167 except Exception:
168 except Exception:
168 log.error(traceback.format_exc())
169 log.error(traceback.format_exc())
169 raise JSONRPCError('failed to create user %s' % username)
170 raise JSONRPCError('failed to create user %s' % username)
170
171
171 @HasPermissionAllDecorator('hg.admin')
172 @HasPermissionAllDecorator('hg.admin')
172 def update_user(self, apiuser, userid, username, password, email,
173 def update_user(self, apiuser, userid, username, password, email,
173 firstname, lastname, active, admin, ldap_dn):
174 firstname, lastname, active, admin, ldap_dn):
174 """
175 """
175 Updates given user
176 Updates given user
176
177
177 :param apiuser:
178 :param apiuser:
178 :param username:
179 :param username:
179 :param password:
180 :param password:
180 :param email:
181 :param email:
181 :param name:
182 :param name:
182 :param lastname:
183 :param lastname:
183 :param active:
184 :param active:
184 :param admin:
185 :param admin:
185 :param ldap_dn:
186 :param ldap_dn:
186 """
187 """
187 if not UserModel().get_user(userid):
188 if not UserModel().get_user(userid):
188 raise JSONRPCError("user %s does not exist" % username)
189 raise JSONRPCError("user %s does not exist" % username)
189
190
190 try:
191 try:
191 usr = UserModel().create_or_update(
192 usr = UserModel().create_or_update(
192 username, password, email, firstname,
193 username, password, email, firstname,
193 lastname, active, admin, ldap_dn
194 lastname, active, admin, ldap_dn
194 )
195 )
195 Session.commit()
196 Session.commit()
196 return dict(
197 return dict(
197 id=usr.user_id,
198 id=usr.user_id,
198 msg='updated user %s' % username
199 msg='updated user %s' % username
199 )
200 )
200 except Exception:
201 except Exception:
201 log.error(traceback.format_exc())
202 log.error(traceback.format_exc())
202 raise JSONRPCError('failed to update user %s' % username)
203 raise JSONRPCError('failed to update user %s' % username)
203
204
204 @HasPermissionAllDecorator('hg.admin')
205 @HasPermissionAllDecorator('hg.admin')
205 def get_users_group(self, apiuser, group_name):
206 def get_users_group(self, apiuser, group_name):
206 """"
207 """"
207 Get users group by name
208 Get users group by name
208
209
209 :param apiuser:
210 :param apiuser:
210 :param group_name:
211 :param group_name:
211 """
212 """
212
213
213 users_group = UsersGroup.get_by_group_name(group_name)
214 users_group = UsersGroup.get_by_group_name(group_name)
214 if not users_group:
215 if not users_group:
215 return None
216 return None
216
217
217 members = []
218 members = []
218 for user in users_group.members:
219 for user in users_group.members:
219 user = user.user
220 user = user.user
220 members.append(dict(id=user.user_id,
221 members.append(dict(id=user.user_id,
221 username=user.username,
222 username=user.username,
222 firstname=user.name,
223 firstname=user.name,
223 lastname=user.lastname,
224 lastname=user.lastname,
224 email=user.email,
225 email=user.email,
225 active=user.active,
226 active=user.active,
226 admin=user.admin,
227 admin=user.admin,
227 ldap=user.ldap_dn))
228 ldap=user.ldap_dn))
228
229
229 return dict(id=users_group.users_group_id,
230 return dict(id=users_group.users_group_id,
230 group_name=users_group.users_group_name,
231 group_name=users_group.users_group_name,
231 active=users_group.users_group_active,
232 active=users_group.users_group_active,
232 members=members)
233 members=members)
233
234
234 @HasPermissionAllDecorator('hg.admin')
235 @HasPermissionAllDecorator('hg.admin')
235 def get_users_groups(self, apiuser):
236 def get_users_groups(self, apiuser):
236 """"
237 """"
237 Get all users groups
238 Get all users groups
238
239
239 :param apiuser:
240 :param apiuser:
240 """
241 """
241
242
242 result = []
243 result = []
243 for users_group in UsersGroup.getAll():
244 for users_group in UsersGroup.getAll():
244 members = []
245 members = []
245 for user in users_group.members:
246 for user in users_group.members:
246 user = user.user
247 user = user.user
247 members.append(dict(id=user.user_id,
248 members.append(dict(id=user.user_id,
248 username=user.username,
249 username=user.username,
249 firstname=user.name,
250 firstname=user.name,
250 lastname=user.lastname,
251 lastname=user.lastname,
251 email=user.email,
252 email=user.email,
252 active=user.active,
253 active=user.active,
253 admin=user.admin,
254 admin=user.admin,
254 ldap=user.ldap_dn))
255 ldap=user.ldap_dn))
255
256
256 result.append(dict(id=users_group.users_group_id,
257 result.append(dict(id=users_group.users_group_id,
257 group_name=users_group.users_group_name,
258 group_name=users_group.users_group_name,
258 active=users_group.users_group_active,
259 active=users_group.users_group_active,
259 members=members))
260 members=members))
260 return result
261 return result
261
262
262 @HasPermissionAllDecorator('hg.admin')
263 @HasPermissionAllDecorator('hg.admin')
263 def create_users_group(self, apiuser, group_name, active=True):
264 def create_users_group(self, apiuser, group_name, active=True):
264 """
265 """
265 Creates an new usergroup
266 Creates an new usergroup
266
267
267 :param group_name:
268 :param group_name:
268 :param active:
269 :param active:
269 """
270 """
270
271
271 if self.get_users_group(apiuser, group_name):
272 if self.get_users_group(apiuser, group_name):
272 raise JSONRPCError("users group %s already exist" % group_name)
273 raise JSONRPCError("users group %s already exist" % group_name)
273
274
274 try:
275 try:
275 ug = UsersGroupModel().create(name=group_name, active=active)
276 ug = UsersGroupModel().create(name=group_name, active=active)
276 Session.commit()
277 Session.commit()
277 return dict(id=ug.users_group_id,
278 return dict(id=ug.users_group_id,
278 msg='created new users group %s' % group_name)
279 msg='created new users group %s' % group_name)
279 except Exception:
280 except Exception:
280 log.error(traceback.format_exc())
281 log.error(traceback.format_exc())
281 raise JSONRPCError('failed to create group %s' % group_name)
282 raise JSONRPCError('failed to create group %s' % group_name)
282
283
283 @HasPermissionAllDecorator('hg.admin')
284 @HasPermissionAllDecorator('hg.admin')
284 def add_user_to_users_group(self, apiuser, group_name, username):
285 def add_user_to_users_group(self, apiuser, group_name, username):
285 """"
286 """"
286 Add a user to a users group
287 Add a user to a users group
287
288
288 :param apiuser:
289 :param apiuser:
289 :param group_name:
290 :param group_name:
290 :param username:
291 :param username:
291 """
292 """
292
293
293 try:
294 try:
294 users_group = UsersGroup.get_by_group_name(group_name)
295 users_group = UsersGroup.get_by_group_name(group_name)
295 if not users_group:
296 if not users_group:
296 raise JSONRPCError('unknown users group %s' % group_name)
297 raise JSONRPCError('unknown users group %s' % group_name)
297
298
298 user = User.get_by_username(username)
299 user = User.get_by_username(username)
299 if user is None:
300 if user is None:
300 raise JSONRPCError('unknown user %s' % username)
301 raise JSONRPCError('unknown user %s' % username)
301
302
302 ugm = UsersGroupModel().add_user_to_group(users_group, user)
303 ugm = UsersGroupModel().add_user_to_group(users_group, user)
303 success = True if ugm != True else False
304 success = True if ugm != True else False
304 msg = 'added member %s to users group %s' % (username, group_name)
305 msg = 'added member %s to users group %s' % (username, group_name)
305 msg = msg if success else 'User is already in that group'
306 msg = msg if success else 'User is already in that group'
306 Session.commit()
307 Session.commit()
307
308
308 return dict(
309 return dict(
309 id=ugm.users_group_member_id if ugm != True else None,
310 id=ugm.users_group_member_id if ugm != True else None,
310 success=success,
311 success=success,
311 msg=msg
312 msg=msg
312 )
313 )
313 except Exception:
314 except Exception:
314 log.error(traceback.format_exc())
315 log.error(traceback.format_exc())
315 raise JSONRPCError('failed to add users group member')
316 raise JSONRPCError('failed to add users group member')
316
317
317 @HasPermissionAllDecorator('hg.admin')
318 @HasPermissionAllDecorator('hg.admin')
318 def remove_user_from_users_group(self, apiuser, group_name, username):
319 def remove_user_from_users_group(self, apiuser, group_name, username):
319 """
320 """
320 Remove user from a group
321 Remove user from a group
321
322
322 :param apiuser
323 :param apiuser
323 :param group_name
324 :param group_name
324 :param username
325 :param username
325 """
326 """
326
327
327 try:
328 try:
328 users_group = UsersGroup.get_by_group_name(group_name)
329 users_group = UsersGroup.get_by_group_name(group_name)
329 if not users_group:
330 if not users_group:
330 raise JSONRPCError('unknown users group %s' % group_name)
331 raise JSONRPCError('unknown users group %s' % group_name)
331
332
332 user = User.get_by_username(username)
333 user = User.get_by_username(username)
333 if user is None:
334 if user is None:
334 raise JSONRPCError('unknown user %s' % username)
335 raise JSONRPCError('unknown user %s' % username)
335
336
336 success = UsersGroupModel().remove_user_from_group(users_group, user)
337 success = UsersGroupModel().remove_user_from_group(users_group, user)
337 msg = 'removed member %s from users group %s' % (username, group_name)
338 msg = 'removed member %s from users group %s' % (username, group_name)
338 msg = msg if success else "User wasn't in group"
339 msg = msg if success else "User wasn't in group"
339 Session.commit()
340 Session.commit()
340 return dict(success=success, msg=msg)
341 return dict(success=success, msg=msg)
341 except Exception:
342 except Exception:
342 log.error(traceback.format_exc())
343 log.error(traceback.format_exc())
343 raise JSONRPCError('failed to remove user from group')
344 raise JSONRPCError('failed to remove user from group')
344
345
345 @HasPermissionAnyDecorator('hg.admin')
346 @HasPermissionAnyDecorator('hg.admin')
346 def get_repo(self, apiuser, repoid):
347 def get_repo(self, apiuser, repoid):
347 """"
348 """"
348 Get repository by name
349 Get repository by name
349
350
350 :param apiuser:
351 :param apiuser:
351 :param repo_name:
352 :param repo_name:
352 """
353 """
353
354
354 repo = RepoModel().get_repo(repoid)
355 repo = RepoModel().get_repo(repoid)
355 if repo is None:
356 if repo is None:
356 raise JSONRPCError('unknown repository %s' % repo)
357 raise JSONRPCError('unknown repository %s' % repo)
357
358
358 members = []
359 members = []
359 for user in repo.repo_to_perm:
360 for user in repo.repo_to_perm:
360 perm = user.permission.permission_name
361 perm = user.permission.permission_name
361 user = user.user
362 user = user.user
362 members.append(
363 members.append(
363 dict(
364 dict(
364 type="user",
365 type="user",
365 id=user.user_id,
366 id=user.user_id,
366 username=user.username,
367 username=user.username,
367 firstname=user.name,
368 firstname=user.name,
368 lastname=user.lastname,
369 lastname=user.lastname,
369 email=user.email,
370 email=user.email,
370 active=user.active,
371 active=user.active,
371 admin=user.admin,
372 admin=user.admin,
372 ldap=user.ldap_dn,
373 ldap=user.ldap_dn,
373 permission=perm
374 permission=perm
374 )
375 )
375 )
376 )
376 for users_group in repo.users_group_to_perm:
377 for users_group in repo.users_group_to_perm:
377 perm = users_group.permission.permission_name
378 perm = users_group.permission.permission_name
378 users_group = users_group.users_group
379 users_group = users_group.users_group
379 members.append(
380 members.append(
380 dict(
381 dict(
381 type="users_group",
382 type="users_group",
382 id=users_group.users_group_id,
383 id=users_group.users_group_id,
383 name=users_group.users_group_name,
384 name=users_group.users_group_name,
384 active=users_group.users_group_active,
385 active=users_group.users_group_active,
385 permission=perm
386 permission=perm
386 )
387 )
387 )
388 )
388
389
389 return dict(
390 return dict(
390 id=repo.repo_id,
391 id=repo.repo_id,
391 repo_name=repo.repo_name,
392 repo_name=repo.repo_name,
392 type=repo.repo_type,
393 type=repo.repo_type,
393 description=repo.description,
394 description=repo.description,
394 members=members
395 members=members
395 )
396 )
396
397
397 @HasPermissionAnyDecorator('hg.admin')
398 @HasPermissionAnyDecorator('hg.admin')
398 def get_repos(self, apiuser):
399 def get_repos(self, apiuser):
399 """"
400 """"
400 Get all repositories
401 Get all repositories
401
402
402 :param apiuser:
403 :param apiuser:
403 """
404 """
404
405
405 result = []
406 result = []
406 for repository in Repository.getAll():
407 for repository in Repository.getAll():
407 result.append(
408 result.append(
408 dict(
409 dict(
409 id=repository.repo_id,
410 id=repository.repo_id,
410 repo_name=repository.repo_name,
411 repo_name=repository.repo_name,
411 type=repository.repo_type,
412 type=repository.repo_type,
412 description=repository.description
413 description=repository.description
413 )
414 )
414 )
415 )
415 return result
416 return result
416
417
417 @HasPermissionAnyDecorator('hg.admin')
418 @HasPermissionAnyDecorator('hg.admin')
418 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
419 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
419 ret_type='all'):
420 ret_type='all'):
420 """
421 """
421 returns a list of nodes and it's children
422 returns a list of nodes and it's children
422 for a given path at given revision. It's possible to specify ret_type
423 for a given path at given revision. It's possible to specify ret_type
423 to show only files or dirs
424 to show only files or dirs
424
425
425 :param apiuser:
426 :param apiuser:
426 :param repo_name: name of repository
427 :param repo_name: name of repository
427 :param revision: revision for which listing should be done
428 :param revision: revision for which listing should be done
428 :param root_path: path from which start displaying
429 :param root_path: path from which start displaying
429 :param ret_type: return type 'all|files|dirs' nodes
430 :param ret_type: return type 'all|files|dirs' nodes
430 """
431 """
431 try:
432 try:
432 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
433 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
433 flat=False)
434 flat=False)
434 _map = {
435 _map = {
435 'all': _d + _f,
436 'all': _d + _f,
436 'files': _f,
437 'files': _f,
437 'dirs': _d,
438 'dirs': _d,
438 }
439 }
439 return _map[ret_type]
440 return _map[ret_type]
440 except KeyError:
441 except KeyError:
441 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
442 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
442 except Exception, e:
443 except Exception, e:
443 raise JSONRPCError(e)
444 raise JSONRPCError(e)
444
445
445 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
446 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
446 def create_repo(self, apiuser, repo_name, owner_name, description='',
447 def create_repo(self, apiuser, repo_name, owner_name, description='',
447 repo_type='hg', private=False, clone_uri=None):
448 repo_type='hg', private=False, clone_uri=None):
448 """
449 """
449 Create repository, if clone_url is given it makes a remote clone
450 Create repository, if clone_url is given it makes a remote clone
450
451
451 :param apiuser:
452 :param apiuser:
452 :param repo_name:
453 :param repo_name:
453 :param owner_name:
454 :param owner_name:
454 :param description:
455 :param description:
455 :param repo_type:
456 :param repo_type:
456 :param private:
457 :param private:
457 :param clone_uri:
458 :param clone_uri:
458 """
459 """
459
460
460 try:
461 try:
461 owner = User.get_by_username(owner_name)
462 owner = User.get_by_username(owner_name)
462 if owner is None:
463 if owner is None:
463 raise JSONRPCError('unknown user %s' % owner_name)
464 raise JSONRPCError('unknown user %s' % owner_name)
464
465
465 if Repository.get_by_repo_name(repo_name):
466 if Repository.get_by_repo_name(repo_name):
466 raise JSONRPCError("repo %s already exist" % repo_name)
467 raise JSONRPCError("repo %s already exist" % repo_name)
467
468
468 groups = repo_name.split(Repository.url_sep())
469 groups = repo_name.split(Repository.url_sep())
469 real_name = groups[-1]
470 real_name = groups[-1]
470 # create structure of groups
471 # create structure of groups
471 group = map_groups(repo_name)
472 group = map_groups(repo_name)
472
473
473 repo = RepoModel().create(
474 repo = RepoModel().create(
474 dict(
475 dict(
475 repo_name=real_name,
476 repo_name=real_name,
476 repo_name_full=repo_name,
477 repo_name_full=repo_name,
477 description=description,
478 description=description,
478 private=private,
479 private=private,
479 repo_type=repo_type,
480 repo_type=repo_type,
480 repo_group=group.group_id if group else None,
481 repo_group=group.group_id if group else None,
481 clone_uri=clone_uri
482 clone_uri=clone_uri
482 ),
483 ),
483 owner
484 owner
484 )
485 )
485 Session.commit()
486 Session.commit()
486
487
487 return dict(
488 return dict(
488 id=repo.repo_id,
489 id=repo.repo_id,
489 msg="Created new repository %s" % repo.repo_name
490 msg="Created new repository %s" % repo.repo_name
490 )
491 )
491
492
492 except Exception:
493 except Exception:
493 log.error(traceback.format_exc())
494 log.error(traceback.format_exc())
494 raise JSONRPCError('failed to create repository %s' % repo_name)
495 raise JSONRPCError('failed to create repository %s' % repo_name)
495
496
496 @HasPermissionAnyDecorator('hg.admin')
497 @HasPermissionAnyDecorator('hg.admin')
497 def delete_repo(self, apiuser, repo_name):
498 def delete_repo(self, apiuser, repo_name):
498 """
499 """
499 Deletes a given repository
500 Deletes a given repository
500
501
501 :param repo_name:
502 :param repo_name:
502 """
503 """
503 if not Repository.get_by_repo_name(repo_name):
504 if not Repository.get_by_repo_name(repo_name):
504 raise JSONRPCError("repo %s does not exist" % repo_name)
505 raise JSONRPCError("repo %s does not exist" % repo_name)
505 try:
506 try:
506 RepoModel().delete(repo_name)
507 RepoModel().delete(repo_name)
507 Session.commit()
508 Session.commit()
508 return dict(
509 return dict(
509 msg='Deleted repository %s' % repo_name
510 msg='Deleted repository %s' % repo_name
510 )
511 )
511 except Exception:
512 except Exception:
512 log.error(traceback.format_exc())
513 log.error(traceback.format_exc())
513 raise JSONRPCError('failed to delete repository %s' % repo_name)
514 raise JSONRPCError('failed to delete repository %s' % repo_name)
514
515
515 @HasPermissionAnyDecorator('hg.admin')
516 @HasPermissionAnyDecorator('hg.admin')
516 def grant_user_permission(self, apiuser, repo_name, username, perm):
517 def grant_user_permission(self, apiuser, repo_name, username, perm):
517 """
518 """
518 Grant permission for user on given repository, or update existing one
519 Grant permission for user on given repository, or update existing one
519 if found
520 if found
520
521
521 :param repo_name:
522 :param repo_name:
522 :param username:
523 :param username:
523 :param perm:
524 :param perm:
524 """
525 """
525
526
526 try:
527 try:
527 repo = Repository.get_by_repo_name(repo_name)
528 repo = Repository.get_by_repo_name(repo_name)
528 if repo is None:
529 if repo is None:
529 raise JSONRPCError('unknown repository %s' % repo)
530 raise JSONRPCError('unknown repository %s' % repo)
530
531
531 user = User.get_by_username(username)
532 user = User.get_by_username(username)
532 if user is None:
533 if user is None:
533 raise JSONRPCError('unknown user %s' % username)
534 raise JSONRPCError('unknown user %s' % username)
534
535
535 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
536 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
536
537
537 Session.commit()
538 Session.commit()
538 return dict(
539 return dict(
539 msg='Granted perm: %s for user: %s in repo: %s' % (
540 msg='Granted perm: %s for user: %s in repo: %s' % (
540 perm, username, repo_name
541 perm, username, repo_name
541 )
542 )
542 )
543 )
543 except Exception:
544 except Exception:
544 log.error(traceback.format_exc())
545 log.error(traceback.format_exc())
545 raise JSONRPCError(
546 raise JSONRPCError(
546 'failed to edit permission %(repo)s for %(user)s' % dict(
547 'failed to edit permission %(repo)s for %(user)s' % dict(
547 user=username, repo=repo_name
548 user=username, repo=repo_name
548 )
549 )
549 )
550 )
550
551
551 @HasPermissionAnyDecorator('hg.admin')
552 @HasPermissionAnyDecorator('hg.admin')
552 def revoke_user_permission(self, apiuser, repo_name, username):
553 def revoke_user_permission(self, apiuser, repo_name, username):
553 """
554 """
554 Revoke permission for user on given repository
555 Revoke permission for user on given repository
555
556
556 :param repo_name:
557 :param repo_name:
557 :param username:
558 :param username:
558 """
559 """
559
560
560 try:
561 try:
561 repo = Repository.get_by_repo_name(repo_name)
562 repo = Repository.get_by_repo_name(repo_name)
562 if repo is None:
563 if repo is None:
563 raise JSONRPCError('unknown repository %s' % repo)
564 raise JSONRPCError('unknown repository %s' % repo)
564
565
565 user = User.get_by_username(username)
566 user = User.get_by_username(username)
566 if user is None:
567 if user is None:
567 raise JSONRPCError('unknown user %s' % username)
568 raise JSONRPCError('unknown user %s' % username)
568
569
569 RepoModel().revoke_user_permission(repo=repo_name, user=username)
570 RepoModel().revoke_user_permission(repo=repo_name, user=username)
570
571
571 Session.commit()
572 Session.commit()
572 return dict(
573 return dict(
573 msg='Revoked perm for user: %s in repo: %s' % (
574 msg='Revoked perm for user: %s in repo: %s' % (
574 username, repo_name
575 username, repo_name
575 )
576 )
576 )
577 )
577 except Exception:
578 except Exception:
578 log.error(traceback.format_exc())
579 log.error(traceback.format_exc())
579 raise JSONRPCError(
580 raise JSONRPCError(
580 'failed to edit permission %(repo)s for %(user)s' % dict(
581 'failed to edit permission %(repo)s for %(user)s' % dict(
581 user=username, repo=repo_name
582 user=username, repo=repo_name
582 )
583 )
583 )
584 )
584
585
585 @HasPermissionAnyDecorator('hg.admin')
586 @HasPermissionAnyDecorator('hg.admin')
586 def grant_users_group_permission(self, apiuser, repo_name, group_name, perm):
587 def grant_users_group_permission(self, apiuser, repo_name, group_name, perm):
587 """
588 """
588 Grant permission for users group on given repository, or update
589 Grant permission for users group on given repository, or update
589 existing one if found
590 existing one if found
590
591
591 :param repo_name:
592 :param repo_name:
592 :param group_name:
593 :param group_name:
593 :param perm:
594 :param perm:
594 """
595 """
595
596
596 try:
597 try:
597 repo = Repository.get_by_repo_name(repo_name)
598 repo = Repository.get_by_repo_name(repo_name)
598 if repo is None:
599 if repo is None:
599 raise JSONRPCError('unknown repository %s' % repo)
600 raise JSONRPCError('unknown repository %s' % repo)
600
601
601 user_group = UsersGroup.get_by_group_name(group_name)
602 user_group = UsersGroup.get_by_group_name(group_name)
602 if user_group is None:
603 if user_group is None:
603 raise JSONRPCError('unknown users group %s' % user_group)
604 raise JSONRPCError('unknown users group %s' % user_group)
604
605
605 RepoModel().grant_users_group_permission(repo=repo_name,
606 RepoModel().grant_users_group_permission(repo=repo_name,
606 group_name=group_name,
607 group_name=group_name,
607 perm=perm)
608 perm=perm)
608
609
609 Session.commit()
610 Session.commit()
610 return dict(
611 return dict(
611 msg='Granted perm: %s for group: %s in repo: %s' % (
612 msg='Granted perm: %s for group: %s in repo: %s' % (
612 perm, group_name, repo_name
613 perm, group_name, repo_name
613 )
614 )
614 )
615 )
615 except Exception:
616 except Exception:
616 log.error(traceback.format_exc())
617 log.error(traceback.format_exc())
617 raise JSONRPCError(
618 raise JSONRPCError(
618 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
619 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
619 usersgr=group_name, repo=repo_name
620 usersgr=group_name, repo=repo_name
620 )
621 )
621 )
622 )
622
623
623 @HasPermissionAnyDecorator('hg.admin')
624 @HasPermissionAnyDecorator('hg.admin')
624 def revoke_users_group_permission(self, apiuser, repo_name, group_name):
625 def revoke_users_group_permission(self, apiuser, repo_name, group_name):
625 """
626 """
626 Revoke permission for users group on given repository
627 Revoke permission for users group on given repository
627
628
628 :param repo_name:
629 :param repo_name:
629 :param group_name:
630 :param group_name:
630 """
631 """
631
632
632 try:
633 try:
633 repo = Repository.get_by_repo_name(repo_name)
634 repo = Repository.get_by_repo_name(repo_name)
634 if repo is None:
635 if repo is None:
635 raise JSONRPCError('unknown repository %s' % repo)
636 raise JSONRPCError('unknown repository %s' % repo)
636
637
637 user_group = UsersGroup.get_by_group_name(group_name)
638 user_group = UsersGroup.get_by_group_name(group_name)
638 if user_group is None:
639 if user_group is None:
639 raise JSONRPCError('unknown users group %s' % user_group)
640 raise JSONRPCError('unknown users group %s' % user_group)
640
641
641 RepoModel().revoke_users_group_permission(repo=repo_name,
642 RepoModel().revoke_users_group_permission(repo=repo_name,
642 group_name=group_name)
643 group_name=group_name)
643
644
644 Session.commit()
645 Session.commit()
645 return dict(
646 return dict(
646 msg='Revoked perm for group: %s in repo: %s' % (
647 msg='Revoked perm for group: %s in repo: %s' % (
647 group_name, repo_name
648 group_name, repo_name
648 )
649 )
649 )
650 )
650 except Exception:
651 except Exception:
651 log.error(traceback.format_exc())
652 log.error(traceback.format_exc())
652 raise JSONRPCError(
653 raise JSONRPCError(
653 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
654 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
654 usersgr=group_name, repo=repo_name
655 usersgr=group_name, repo=repo_name
655 )
656 )
656 )
657 )
@@ -1,399 +1,438 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.compat
3 rhodecode.lib.compat
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Python backward compatibility functions and common libs
6 Python backward compatibility functions and common libs
7
7
8
8
9 :created_on: Oct 7, 2011
9 :created_on: Oct 7, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import datetime
29 import functools
28 from rhodecode import __platform__, PLATFORM_WIN
30 from rhodecode import __platform__, PLATFORM_WIN
29
31
30 #==============================================================================
32 #==============================================================================
31 # json
33 # json
32 #==============================================================================
34 #==============================================================================
35
36
37 def __obj_dump(obj):
38 DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
39 DATE_FORMAT = "%Y-%m-%d"
40 if isinstance(obj, complex):
41 return [obj.real, obj.imag]
42 elif isinstance(obj, datetime.datetime):
43 return obj.strftime(DATETIME_FORMAT)
44 elif isinstance(obj, datetime.date):
45 return obj.strftime(DATE_FORMAT)
46 elif isinstance(obj, set):
47 return list(obj)
48 elif isinstance(obj, OrderedDict):
49 return obj.as_dict()
50 else:
51 raise NotImplementedError
52
33 try:
53 try:
34 import json
54 import json
55
56 # extended JSON encoder for json
57 class ExtendedEncoder(json.JSONEncoder):
58 def default(self, obj):
59 try:
60 return __obj_dump(obj)
61 except NotImplementedError:
62 pass
63 return json.JSONEncoder.default(self, obj)
64 # monkey-patch JSON encoder to use extended version
65 json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
35 except ImportError:
66 except ImportError:
36 import simplejson as json
67 import simplejson as json
37
68
69 def extended_encode(obj):
70 try:
71 return __obj_dump(obj)
72 except NotImplementedError:
73 pass
74 raise TypeError("%r is not JSON serializable" % (obj,))
75 json.dumps = functools.partial(json.dumps, default=extended_encode)
76
38
77
39 #==============================================================================
78 #==============================================================================
40 # izip_longest
79 # izip_longest
41 #==============================================================================
80 #==============================================================================
42 try:
81 try:
43 from itertools import izip_longest
82 from itertools import izip_longest
44 except ImportError:
83 except ImportError:
45 import itertools
84 import itertools
46
85
47 def izip_longest(*args, **kwds): # noqa
86 def izip_longest(*args, **kwds):
48 fillvalue = kwds.get("fillvalue")
87 fillvalue = kwds.get("fillvalue")
49
88
50 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
89 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
51 yield counter() # yields the fillvalue, or raises IndexError
90 yield counter() # yields the fillvalue, or raises IndexError
52
91
53 fillers = itertools.repeat(fillvalue)
92 fillers = itertools.repeat(fillvalue)
54 iters = [itertools.chain(it, sentinel(), fillers)
93 iters = [itertools.chain(it, sentinel(), fillers)
55 for it in args]
94 for it in args]
56 try:
95 try:
57 for tup in itertools.izip(*iters):
96 for tup in itertools.izip(*iters):
58 yield tup
97 yield tup
59 except IndexError:
98 except IndexError:
60 pass
99 pass
61
100
62
101
63 #==============================================================================
102 #==============================================================================
64 # OrderedDict
103 # OrderedDict
65 #==============================================================================
104 #==============================================================================
66
105
67 # Python Software Foundation License
106 # Python Software Foundation License
68
107
69 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
108 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
70 # "!=" should be faster.
109 # "!=" should be faster.
71 class _Nil(object):
110 class _Nil(object):
72
111
73 def __repr__(self):
112 def __repr__(self):
74 return "nil"
113 return "nil"
75
114
76 def __eq__(self, other):
115 def __eq__(self, other):
77 if (isinstance(other, _Nil)):
116 if (isinstance(other, _Nil)):
78 return True
117 return True
79 else:
118 else:
80 return NotImplemented
119 return NotImplemented
81
120
82 def __ne__(self, other):
121 def __ne__(self, other):
83 if (isinstance(other, _Nil)):
122 if (isinstance(other, _Nil)):
84 return False
123 return False
85 else:
124 else:
86 return NotImplemented
125 return NotImplemented
87
126
88 _nil = _Nil()
127 _nil = _Nil()
89
128
90
129
91 class _odict(object):
130 class _odict(object):
92 """Ordered dict data structure, with O(1) complexity for dict operations
131 """Ordered dict data structure, with O(1) complexity for dict operations
93 that modify one element.
132 that modify one element.
94
133
95 Overwriting values doesn't change their original sequential order.
134 Overwriting values doesn't change their original sequential order.
96 """
135 """
97
136
98 def _dict_impl(self):
137 def _dict_impl(self):
99 return None
138 return None
100
139
101 def __init__(self, data=(), **kwds):
140 def __init__(self, data=(), **kwds):
102 """This doesn't accept keyword initialization as normal dicts to avoid
141 """This doesn't accept keyword initialization as normal dicts to avoid
103 a trap - inside a function or method the keyword args are accessible
142 a trap - inside a function or method the keyword args are accessible
104 only as a dict, without a defined order, so their original order is
143 only as a dict, without a defined order, so their original order is
105 lost.
144 lost.
106 """
145 """
107 if kwds:
146 if kwds:
108 raise TypeError("__init__() of ordered dict takes no keyword "
147 raise TypeError("__init__() of ordered dict takes no keyword "
109 "arguments to avoid an ordering trap.")
148 "arguments to avoid an ordering trap.")
110 self._dict_impl().__init__(self)
149 self._dict_impl().__init__(self)
111 # If you give a normal dict, then the order of elements is undefined
150 # If you give a normal dict, then the order of elements is undefined
112 if hasattr(data, "iteritems"):
151 if hasattr(data, "iteritems"):
113 for key, val in data.iteritems():
152 for key, val in data.iteritems():
114 self[key] = val
153 self[key] = val
115 else:
154 else:
116 for key, val in data:
155 for key, val in data:
117 self[key] = val
156 self[key] = val
118
157
119 # Double-linked list header
158 # Double-linked list header
120 def _get_lh(self):
159 def _get_lh(self):
121 dict_impl = self._dict_impl()
160 dict_impl = self._dict_impl()
122 if not hasattr(self, '_lh'):
161 if not hasattr(self, '_lh'):
123 dict_impl.__setattr__(self, '_lh', _nil)
162 dict_impl.__setattr__(self, '_lh', _nil)
124 return dict_impl.__getattribute__(self, '_lh')
163 return dict_impl.__getattribute__(self, '_lh')
125
164
126 def _set_lh(self, val):
165 def _set_lh(self, val):
127 self._dict_impl().__setattr__(self, '_lh', val)
166 self._dict_impl().__setattr__(self, '_lh', val)
128
167
129 lh = property(_get_lh, _set_lh)
168 lh = property(_get_lh, _set_lh)
130
169
131 # Double-linked list tail
170 # Double-linked list tail
132 def _get_lt(self):
171 def _get_lt(self):
133 dict_impl = self._dict_impl()
172 dict_impl = self._dict_impl()
134 if not hasattr(self, '_lt'):
173 if not hasattr(self, '_lt'):
135 dict_impl.__setattr__(self, '_lt', _nil)
174 dict_impl.__setattr__(self, '_lt', _nil)
136 return dict_impl.__getattribute__(self, '_lt')
175 return dict_impl.__getattribute__(self, '_lt')
137
176
138 def _set_lt(self, val):
177 def _set_lt(self, val):
139 self._dict_impl().__setattr__(self, '_lt', val)
178 self._dict_impl().__setattr__(self, '_lt', val)
140
179
141 lt = property(_get_lt, _set_lt)
180 lt = property(_get_lt, _set_lt)
142
181
143 def __getitem__(self, key):
182 def __getitem__(self, key):
144 return self._dict_impl().__getitem__(self, key)[1]
183 return self._dict_impl().__getitem__(self, key)[1]
145
184
146 def __setitem__(self, key, val):
185 def __setitem__(self, key, val):
147 dict_impl = self._dict_impl()
186 dict_impl = self._dict_impl()
148 try:
187 try:
149 dict_impl.__getitem__(self, key)[1] = val
188 dict_impl.__getitem__(self, key)[1] = val
150 except KeyError:
189 except KeyError:
151 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
190 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
152 dict_impl.__setitem__(self, key, new)
191 dict_impl.__setitem__(self, key, new)
153 if dict_impl.__getattribute__(self, 'lt') == _nil:
192 if dict_impl.__getattribute__(self, 'lt') == _nil:
154 dict_impl.__setattr__(self, 'lh', key)
193 dict_impl.__setattr__(self, 'lh', key)
155 else:
194 else:
156 dict_impl.__getitem__(
195 dict_impl.__getitem__(
157 self, dict_impl.__getattribute__(self, 'lt'))[2] = key
196 self, dict_impl.__getattribute__(self, 'lt'))[2] = key
158 dict_impl.__setattr__(self, 'lt', key)
197 dict_impl.__setattr__(self, 'lt', key)
159
198
160 def __delitem__(self, key):
199 def __delitem__(self, key):
161 dict_impl = self._dict_impl()
200 dict_impl = self._dict_impl()
162 pred, _, succ = self._dict_impl().__getitem__(self, key)
201 pred, _, succ = self._dict_impl().__getitem__(self, key)
163 if pred == _nil:
202 if pred == _nil:
164 dict_impl.__setattr__(self, 'lh', succ)
203 dict_impl.__setattr__(self, 'lh', succ)
165 else:
204 else:
166 dict_impl.__getitem__(self, pred)[2] = succ
205 dict_impl.__getitem__(self, pred)[2] = succ
167 if succ == _nil:
206 if succ == _nil:
168 dict_impl.__setattr__(self, 'lt', pred)
207 dict_impl.__setattr__(self, 'lt', pred)
169 else:
208 else:
170 dict_impl.__getitem__(self, succ)[0] = pred
209 dict_impl.__getitem__(self, succ)[0] = pred
171 dict_impl.__delitem__(self, key)
210 dict_impl.__delitem__(self, key)
172
211
173 def __contains__(self, key):
212 def __contains__(self, key):
174 return key in self.keys()
213 return key in self.keys()
175
214
176 def __len__(self):
215 def __len__(self):
177 return len(self.keys())
216 return len(self.keys())
178
217
179 def __str__(self):
218 def __str__(self):
180 pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
219 pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
181 return "{%s}" % ", ".join(pairs)
220 return "{%s}" % ", ".join(pairs)
182
221
183 def __repr__(self):
222 def __repr__(self):
184 if self:
223 if self:
185 pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
224 pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
186 return "odict([%s])" % ", ".join(pairs)
225 return "odict([%s])" % ", ".join(pairs)
187 else:
226 else:
188 return "odict()"
227 return "odict()"
189
228
190 def get(self, k, x=None):
229 def get(self, k, x=None):
191 if k in self:
230 if k in self:
192 return self._dict_impl().__getitem__(self, k)[1]
231 return self._dict_impl().__getitem__(self, k)[1]
193 else:
232 else:
194 return x
233 return x
195
234
196 def __iter__(self):
235 def __iter__(self):
197 dict_impl = self._dict_impl()
236 dict_impl = self._dict_impl()
198 curr_key = dict_impl.__getattribute__(self, 'lh')
237 curr_key = dict_impl.__getattribute__(self, 'lh')
199 while curr_key != _nil:
238 while curr_key != _nil:
200 yield curr_key
239 yield curr_key
201 curr_key = dict_impl.__getitem__(self, curr_key)[2]
240 curr_key = dict_impl.__getitem__(self, curr_key)[2]
202
241
203 iterkeys = __iter__
242 iterkeys = __iter__
204
243
205 def keys(self):
244 def keys(self):
206 return list(self.iterkeys())
245 return list(self.iterkeys())
207
246
208 def itervalues(self):
247 def itervalues(self):
209 dict_impl = self._dict_impl()
248 dict_impl = self._dict_impl()
210 curr_key = dict_impl.__getattribute__(self, 'lh')
249 curr_key = dict_impl.__getattribute__(self, 'lh')
211 while curr_key != _nil:
250 while curr_key != _nil:
212 _, val, curr_key = dict_impl.__getitem__(self, curr_key)
251 _, val, curr_key = dict_impl.__getitem__(self, curr_key)
213 yield val
252 yield val
214
253
215 def values(self):
254 def values(self):
216 return list(self.itervalues())
255 return list(self.itervalues())
217
256
218 def iteritems(self):
257 def iteritems(self):
219 dict_impl = self._dict_impl()
258 dict_impl = self._dict_impl()
220 curr_key = dict_impl.__getattribute__(self, 'lh')
259 curr_key = dict_impl.__getattribute__(self, 'lh')
221 while curr_key != _nil:
260 while curr_key != _nil:
222 _, val, next_key = dict_impl.__getitem__(self, curr_key)
261 _, val, next_key = dict_impl.__getitem__(self, curr_key)
223 yield curr_key, val
262 yield curr_key, val
224 curr_key = next_key
263 curr_key = next_key
225
264
226 def items(self):
265 def items(self):
227 return list(self.iteritems())
266 return list(self.iteritems())
228
267
229 def sort(self, cmp=None, key=None, reverse=False):
268 def sort(self, cmp=None, key=None, reverse=False):
230 items = [(k, v) for k, v in self.items()]
269 items = [(k, v) for k, v in self.items()]
231 if cmp is not None:
270 if cmp is not None:
232 items = sorted(items, cmp=cmp)
271 items = sorted(items, cmp=cmp)
233 elif key is not None:
272 elif key is not None:
234 items = sorted(items, key=key)
273 items = sorted(items, key=key)
235 else:
274 else:
236 items = sorted(items, key=lambda x: x[1])
275 items = sorted(items, key=lambda x: x[1])
237 if reverse:
276 if reverse:
238 items.reverse()
277 items.reverse()
239 self.clear()
278 self.clear()
240 self.__init__(items)
279 self.__init__(items)
241
280
242 def clear(self):
281 def clear(self):
243 dict_impl = self._dict_impl()
282 dict_impl = self._dict_impl()
244 dict_impl.clear(self)
283 dict_impl.clear(self)
245 dict_impl.__setattr__(self, 'lh', _nil)
284 dict_impl.__setattr__(self, 'lh', _nil)
246 dict_impl.__setattr__(self, 'lt', _nil)
285 dict_impl.__setattr__(self, 'lt', _nil)
247
286
248 def copy(self):
287 def copy(self):
249 return self.__class__(self)
288 return self.__class__(self)
250
289
251 def update(self, data=(), **kwds):
290 def update(self, data=(), **kwds):
252 if kwds:
291 if kwds:
253 raise TypeError("update() of ordered dict takes no keyword "
292 raise TypeError("update() of ordered dict takes no keyword "
254 "arguments to avoid an ordering trap.")
293 "arguments to avoid an ordering trap.")
255 if hasattr(data, "iteritems"):
294 if hasattr(data, "iteritems"):
256 data = data.iteritems()
295 data = data.iteritems()
257 for key, val in data:
296 for key, val in data:
258 self[key] = val
297 self[key] = val
259
298
260 def setdefault(self, k, x=None):
299 def setdefault(self, k, x=None):
261 try:
300 try:
262 return self[k]
301 return self[k]
263 except KeyError:
302 except KeyError:
264 self[k] = x
303 self[k] = x
265 return x
304 return x
266
305
267 def pop(self, k, x=_nil):
306 def pop(self, k, x=_nil):
268 try:
307 try:
269 val = self[k]
308 val = self[k]
270 del self[k]
309 del self[k]
271 return val
310 return val
272 except KeyError:
311 except KeyError:
273 if x == _nil:
312 if x == _nil:
274 raise
313 raise
275 return x
314 return x
276
315
277 def popitem(self):
316 def popitem(self):
278 try:
317 try:
279 dict_impl = self._dict_impl()
318 dict_impl = self._dict_impl()
280 key = dict_impl.__getattribute__(self, 'lt')
319 key = dict_impl.__getattribute__(self, 'lt')
281 return key, self.pop(key)
320 return key, self.pop(key)
282 except KeyError:
321 except KeyError:
283 raise KeyError("'popitem(): ordered dictionary is empty'")
322 raise KeyError("'popitem(): ordered dictionary is empty'")
284
323
285 def riterkeys(self):
324 def riterkeys(self):
286 """To iterate on keys in reversed order.
325 """To iterate on keys in reversed order.
287 """
326 """
288 dict_impl = self._dict_impl()
327 dict_impl = self._dict_impl()
289 curr_key = dict_impl.__getattribute__(self, 'lt')
328 curr_key = dict_impl.__getattribute__(self, 'lt')
290 while curr_key != _nil:
329 while curr_key != _nil:
291 yield curr_key
330 yield curr_key
292 curr_key = dict_impl.__getitem__(self, curr_key)[0]
331 curr_key = dict_impl.__getitem__(self, curr_key)[0]
293
332
294 __reversed__ = riterkeys
333 __reversed__ = riterkeys
295
334
296 def rkeys(self):
335 def rkeys(self):
297 """List of the keys in reversed order.
336 """List of the keys in reversed order.
298 """
337 """
299 return list(self.riterkeys())
338 return list(self.riterkeys())
300
339
301 def ritervalues(self):
340 def ritervalues(self):
302 """To iterate on values in reversed order.
341 """To iterate on values in reversed order.
303 """
342 """
304 dict_impl = self._dict_impl()
343 dict_impl = self._dict_impl()
305 curr_key = dict_impl.__getattribute__(self, 'lt')
344 curr_key = dict_impl.__getattribute__(self, 'lt')
306 while curr_key != _nil:
345 while curr_key != _nil:
307 curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
346 curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
308 yield val
347 yield val
309
348
310 def rvalues(self):
349 def rvalues(self):
311 """List of the values in reversed order.
350 """List of the values in reversed order.
312 """
351 """
313 return list(self.ritervalues())
352 return list(self.ritervalues())
314
353
315 def riteritems(self):
354 def riteritems(self):
316 """To iterate on (key, value) in reversed order.
355 """To iterate on (key, value) in reversed order.
317 """
356 """
318 dict_impl = self._dict_impl()
357 dict_impl = self._dict_impl()
319 curr_key = dict_impl.__getattribute__(self, 'lt')
358 curr_key = dict_impl.__getattribute__(self, 'lt')
320 while curr_key != _nil:
359 while curr_key != _nil:
321 pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
360 pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
322 yield curr_key, val
361 yield curr_key, val
323 curr_key = pred_key
362 curr_key = pred_key
324
363
325 def ritems(self):
364 def ritems(self):
326 """List of the (key, value) in reversed order.
365 """List of the (key, value) in reversed order.
327 """
366 """
328 return list(self.riteritems())
367 return list(self.riteritems())
329
368
330 def firstkey(self):
369 def firstkey(self):
331 if self:
370 if self:
332 return self._dict_impl().__getattribute__(self, 'lh')
371 return self._dict_impl().__getattribute__(self, 'lh')
333 else:
372 else:
334 raise KeyError("'firstkey(): ordered dictionary is empty'")
373 raise KeyError("'firstkey(): ordered dictionary is empty'")
335
374
336 def lastkey(self):
375 def lastkey(self):
337 if self:
376 if self:
338 return self._dict_impl().__getattribute__(self, 'lt')
377 return self._dict_impl().__getattribute__(self, 'lt')
339 else:
378 else:
340 raise KeyError("'lastkey(): ordered dictionary is empty'")
379 raise KeyError("'lastkey(): ordered dictionary is empty'")
341
380
342 def as_dict(self):
381 def as_dict(self):
343 return self._dict_impl()(self.items())
382 return self._dict_impl()(self.items())
344
383
345 def _repr(self):
384 def _repr(self):
346 """_repr(): low level repr of the whole data contained in the odict.
385 """_repr(): low level repr of the whole data contained in the odict.
347 Useful for debugging.
386 Useful for debugging.
348 """
387 """
349 dict_impl = self._dict_impl()
388 dict_impl = self._dict_impl()
350 form = "odict low level repr lh,lt,data: %r, %r, %s"
389 form = "odict low level repr lh,lt,data: %r, %r, %s"
351 return form % (dict_impl.__getattribute__(self, 'lh'),
390 return form % (dict_impl.__getattribute__(self, 'lh'),
352 dict_impl.__getattribute__(self, 'lt'),
391 dict_impl.__getattribute__(self, 'lt'),
353 dict_impl.__repr__(self))
392 dict_impl.__repr__(self))
354
393
355
394
356 class OrderedDict(_odict, dict):
395 class OrderedDict(_odict, dict):
357
396
358 def _dict_impl(self):
397 def _dict_impl(self):
359 return dict
398 return dict
360
399
361
400
362 #==============================================================================
401 #==============================================================================
363 # OrderedSet
402 # OrderedSet
364 #==============================================================================
403 #==============================================================================
365 from sqlalchemy.util import OrderedSet
404 from sqlalchemy.util import OrderedSet
366
405
367
406
368 #==============================================================================
407 #==============================================================================
369 # kill FUNCTIONS
408 # kill FUNCTIONS
370 #==============================================================================
409 #==============================================================================
371 if __platform__ in PLATFORM_WIN:
410 if __platform__ in PLATFORM_WIN:
372 import ctypes
411 import ctypes
373
412
374 def kill(pid, sig):
413 def kill(pid, sig):
375 """kill function for Win32"""
414 """kill function for Win32"""
376 kernel32 = ctypes.windll.kernel32
415 kernel32 = ctypes.windll.kernel32
377 handle = kernel32.OpenProcess(1, 0, pid)
416 handle = kernel32.OpenProcess(1, 0, pid)
378 return (0 != kernel32.TerminateProcess(handle, 0))
417 return (0 != kernel32.TerminateProcess(handle, 0))
379
418
380 else:
419 else:
381 kill = os.kill
420 kill = os.kill
382
421
383
422
384 #==============================================================================
423 #==============================================================================
385 # itertools.product
424 # itertools.product
386 #==============================================================================
425 #==============================================================================
387
426
388 try:
427 try:
389 from itertools import product
428 from itertools import product
390 except ImportError:
429 except ImportError:
391 def product(*args, **kwds):
430 def product(*args, **kwds):
392 # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
431 # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
393 # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
432 # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
394 pools = map(tuple, args) * kwds.get('repeat', 1)
433 pools = map(tuple, args) * kwds.get('repeat', 1)
395 result = [[]]
434 result = [[]]
396 for pool in pools:
435 for pool in pools:
397 result = [x + [y] for x in result for y in pool]
436 result = [x + [y] for x in result for y in pool]
398 for prod in result:
437 for prod in result:
399 yield tuple(prod)
438 yield tuple(prod)
General Comments 0
You need to be logged in to leave comments. Login now