##// END OF EJS Templates
created rhodecode-api binary script for working with api via cli...
marcink -
r2379:7ac09514 beta
parent child Browse files
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,216 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.bin.backup_manager
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Api CLI client for RhodeCode
7
8 :created_on: Jun 3, 2012
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 from __future__ import with_statement
27 import os
28 import sys
29 import random
30 import urllib2
31 import pprint
32 import argparse
33
34 try:
35 from rhodecode.lib.ext_json import json
36 except ImportError:
37 try:
38 import simplejson as json
39 except ImportError:
40 import json
41
42
43 CONFIG_NAME = '.rhodecode'
44
45
46 class RcConf(object):
47 """
48 RhodeCode config for API
49
50 conf = RcConf()
51 conf['key']
52
53 """
54
55 def __init__(self, autoload=True, autocreate=False, config=None):
56 self._conf_name = CONFIG_NAME
57 self._conf = {}
58 if autocreate:
59 self.make_config(config)
60 if autoload:
61 self._conf = self.load_config()
62
63 def __getitem__(self, key):
64 return self._conf[key]
65
66 def __nonzero__(self):
67 if self._conf:
68 return True
69 return False
70
71 def __eq__(self):
72 return self._conf.__eq__()
73
74 def __repr__(self):
75 return 'RcConf<%s>' % self._conf.__repr__()
76
77 def make_config(self, config):
78 """
79 Saves given config as a JSON dump in the _conf_name location
80
81 :param config:
82 :type config:
83 """
84 with open(self._conf_name, 'wb') as f:
85 json.dump(config, f, indent=4)
86 sys.stdout.write('Updated conf\n')
87
88 def update_config(self, new_config):
89 """
90 Reads the JSON config updates it's values with new_config and
91 saves it back as JSON dump
92
93 :param new_config:
94 """
95 config = {}
96 try:
97 with open(self._conf_name, 'rb') as conf:
98 config = json.load(conf)
99 except IOError, e:
100 sys.stderr.write(str(e) + '\n')
101
102 config.update(new_config)
103 self.make_config(config)
104
105 def load_config(self):
106 """
107 Loads config from file and returns loaded JSON object
108 """
109 try:
110 with open(self._conf_name, 'rb') as conf:
111 return json.load(conf)
112 except IOError, e:
113 #sys.stderr.write(str(e) + '\n')
114 pass
115
116
117 def api_call(apikey, apihost, method=None, **kw):
118 """
119 Api_call wrapper for RhodeCode
120
121 :param apikey:
122 :param apihost:
123 :param method:
124 """
125 def _build_data(random_id):
126 """
127 Builds API data with given random ID
128
129 :param random_id:
130 :type random_id:
131 """
132 return {
133 "id": random_id,
134 "api_key": apikey,
135 "method": method,
136 "args": kw
137 }
138
139 if not method:
140 raise Exception('please specify method name !')
141 id_ = random.randrange(1, 200)
142 req = urllib2.Request('%s/_admin/api' % apihost,
143 data=json.dumps(_build_data(id_)),
144 headers={'content-type': 'text/plain'})
145 print 'calling %s to %s' % (req.get_data(), apihost)
146 ret = urllib2.urlopen(req)
147 json_data = json.loads(ret.read())
148 id_ret = json_data['id']
149 _formatted_json = pprint.pformat(json_data)
150 if id_ret == id_:
151 print 'rhodecode said:\n%s' % (_formatted_json)
152 else:
153 raise Exception('something went wrong. '
154 'ID mismatch got %s, expected %s | %s' % (
155 id_ret, id_, _formatted_json))
156
157
158 def argparser(argv):
159 usage = ("rhodecode_api [-h] [--apikey APIKEY] [--apihost APIHOST] "
160 "_create_config or METHOD <key:val> <key2:val> ...")
161
162 parser = argparse.ArgumentParser(description='RhodeCode API cli',
163 usage=usage)
164
165 ## config
166 group = parser.add_argument_group('config')
167 group.add_argument('--apikey', help='api access key')
168 group.add_argument('--apihost', help='api host')
169
170 group = parser.add_argument_group('API')
171 group.add_argument('method', metavar='METHOD', type=str,
172 help='API method name to call followed by key:value attributes',
173 )
174
175 args, other = parser.parse_known_args()
176 return parser, args, other
177
178
179 def main(argv=None):
180 """
181 Main execution function for cli
182
183 :param argv:
184 :type argv:
185 """
186 if argv is None:
187 argv = sys.argv
188
189 conf = None
190 parser, args, other = argparser(argv)
191
192 api_credentials_given = (args.apikey and args.apihost)
193 if args.method == '_create_config':
194 if not api_credentials_given:
195 raise parser.error('_create_config requires --apikey and --apihost')
196 conf = RcConf(autocreate=True, config={'apikey': args.apikey,
197 'apihost': args.apihost})
198 sys.stdout.write('Create new config in %s\n' % CONFIG_NAME)
199
200 if not conf:
201 conf = RcConf(autoload=True)
202 if not conf:
203 if not api_credentials_given:
204 parser.error('Could not find config file and missing '
205 '--apikey or --apihost in params')
206
207 apikey = args.apikey or conf['apikey']
208 host = args.apihost or conf['apihost']
209 method = args.method
210 margs = dict(map(lambda s: s.split(':', 1), other))
211
212 api_call(apikey, host, method, **margs)
213 return 0
214
215 if __name__ == '__main__':
216 sys.exit(main(sys.argv))
@@ -1,21 +1,22 b''
1 syntax: glob
1 syntax: glob
2 *.pyc
2 *.pyc
3 *.swp
3 *.swp
4 *.sqlite
4 *.sqlite
5 Paste*.egg
5 Paste*.egg
6
6
7 syntax: regexp
7 syntax: regexp
8 ^rcextensions
8 ^rcextensions
9 ^build
9 ^build
10 ^docs/build/
10 ^docs/build/
11 ^docs/_build/
11 ^docs/_build/
12 ^data$
12 ^data$
13 ^\.settings$
13 ^\.settings$
14 ^\.project$
14 ^\.project$
15 ^\.pydevproject$
15 ^\.pydevproject$
16 ^\.coverage$
16 ^\.coverage$
17 ^rhodecode\.db$
17 ^rhodecode\.db$
18 ^test\.db$
18 ^test\.db$
19 ^RhodeCode\.egg-info$
19 ^RhodeCode\.egg-info$
20 ^rc\.ini$
20 ^rc\.ini$
21 ^fabfile.py
21 ^fabfile.py
22 ^\.rhodecode$
@@ -1,711 +1,752 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
63 API CLIENT
64 ++++++++++
65
66 From version 1.4 RhodeCode adds a binary script that allows to easily
67 communicate with API. After installing RhodeCode a `rhodecode-api` script
68 will be available.
69
70 To get started quickly simply run::
71
72 rhodecode-api _create_config --apikey=<youapikey> --apihost=<rhodecode host>
73
74 This will create a file named .config in the directory you executed it storing
75 json config file with credentials. You can skip this step and always provide
76 both of the arguments to be able to communicate with server
77
78
79 after that simply run any api command for example get_repo::
80
81 rhodecode-api get_repo
82
83 calling {"api_key": "<apikey>", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000
84 rhodecode said:
85 {'error': 'Missing non optional `repoid` arg in JSON DATA',
86 'id': 75,
87 'result': None}
88
89 Ups looks like we forgot to add an argument
90
91 Let's try again now giving the repoid as parameters::
92
93 rhodecode-api get_repo repoid:rhodecode
94
95 calling {"api_key": "<apikey>", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000
96 rhodecode said:
97 {'error': None,
98 'id': 39,
99 'result': <json data...>}
100
101
102
62 API METHODS
103 API METHODS
63 +++++++++++
104 +++++++++++
64
105
65
106
66 pull
107 pull
67 ----
108 ----
68
109
69 Pulls given repo from remote location. Can be used to automatically keep
110 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
111 remote repos up to date. This command can be executed only using api_key
71 belonging to user with admin rights
112 belonging to user with admin rights
72
113
73 INPUT::
114 INPUT::
74
115
75 id : <id_for_response>
116 id : <id_for_response>
76 api_key : "<api_key>"
117 api_key : "<api_key>"
77 method : "pull"
118 method : "pull"
78 args : {
119 args : {
79 "repo_name" : "<reponame>"
120 "repo_name" : "<reponame>"
80 }
121 }
81
122
82 OUTPUT::
123 OUTPUT::
83
124
84 result : "Pulled from <reponame>"
125 result : "Pulled from <reponame>"
85 error : null
126 error : null
86
127
87
128
88 get_user
129 get_user
89 --------
130 --------
90
131
91 Get's an user by username or user_id, Returns empty result if user is not found.
132 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
133 This command can be executed only using api_key belonging to user with admin
93 rights.
134 rights.
94
135
95
136
96 INPUT::
137 INPUT::
97
138
98 id : <id_for_response>
139 id : <id_for_response>
99 api_key : "<api_key>"
140 api_key : "<api_key>"
100 method : "get_user"
141 method : "get_user"
101 args : {
142 args : {
102 "userid" : "<username or user_id>"
143 "userid" : "<username or user_id>"
103 }
144 }
104
145
105 OUTPUT::
146 OUTPUT::
106
147
107 result: None if user does not exist or
148 result: None if user does not exist or
108 {
149 {
109 "id" : "<id>",
150 "id" : "<id>",
110 "username" : "<username>",
151 "username" : "<username>",
111 "firstname": "<firstname>",
152 "firstname": "<firstname>",
112 "lastname" : "<lastname>",
153 "lastname" : "<lastname>",
113 "email" : "<email>",
154 "email" : "<email>",
114 "active" : "<bool>",
155 "active" : "<bool>",
115 "admin" :Β  "<bool>",
156 "admin" :Β  "<bool>",
116 "ldap_dn" : "<ldap_dn>",
157 "ldap_dn" : "<ldap_dn>",
117 "last_login": "<last_login>",
158 "last_login": "<last_login>",
118 "permissions": {
159 "permissions": {
119 "global": ["hg.create.repository",
160 "global": ["hg.create.repository",
120 "repository.read",
161 "repository.read",
121 "hg.register.manual_activate"],
162 "hg.register.manual_activate"],
122 "repositories": {"repo1": "repository.none"},
163 "repositories": {"repo1": "repository.none"},
123 "repositories_groups": {"Group1": "group.read"}
164 "repositories_groups": {"Group1": "group.read"}
124 },
165 },
125 }
166 }
126
167
127 error: null
168 error: null
128
169
129
170
130 get_users
171 get_users
131 ---------
172 ---------
132
173
133 Lists all existing users. This command can be executed only using api_key
174 Lists all existing users. This command can be executed only using api_key
134 belonging to user with admin rights.
175 belonging to user with admin rights.
135
176
136
177
137 INPUT::
178 INPUT::
138
179
139 id : <id_for_response>
180 id : <id_for_response>
140 api_key : "<api_key>"
181 api_key : "<api_key>"
141 method : "get_users"
182 method : "get_users"
142 args : { }
183 args : { }
143
184
144 OUTPUT::
185 OUTPUT::
145
186
146 result: [
187 result: [
147 {
188 {
148 "id" : "<id>",
189 "id" : "<id>",
149 "username" : "<username>",
190 "username" : "<username>",
150 "firstname": "<firstname>",
191 "firstname": "<firstname>",
151 "lastname" : "<lastname>",
192 "lastname" : "<lastname>",
152 "email" : "<email>",
193 "email" : "<email>",
153 "active" : "<bool>",
194 "active" : "<bool>",
154 "admin" :Β  "<bool>",
195 "admin" :Β  "<bool>",
155 "ldap_dn" : "<ldap_dn>",
196 "ldap_dn" : "<ldap_dn>",
156 "last_login": "<last_login>",
197 "last_login": "<last_login>",
157 },
198 },
158 …
199 …
159 ]
200 ]
160 error: null
201 error: null
161
202
162
203
163 create_user
204 create_user
164 -----------
205 -----------
165
206
166 Creates new user. This command can
207 Creates new user. This command can
167 be executed only using api_key belonging to user with admin rights.
208 be executed only using api_key belonging to user with admin rights.
168
209
169
210
170 INPUT::
211 INPUT::
171
212
172 id : <id_for_response>
213 id : <id_for_response>
173 api_key : "<api_key>"
214 api_key : "<api_key>"
174 method : "create_user"
215 method : "create_user"
175 args : {
216 args : {
176 "username" : "<username>",
217 "username" : "<username>",
177 "password" : "<password>",
218 "password" : "<password>",
178 "email" : "<useremail>",
219 "email" : "<useremail>",
179 "firstname" : "<firstname> = None",
220 "firstname" : "<firstname> = None",
180 "lastname" : "<lastname> = None",
221 "lastname" : "<lastname> = None",
181 "active" : "<bool> = True",
222 "active" : "<bool> = True",
182 "admin" : "<bool> = False",
223 "admin" : "<bool> = False",
183 "ldap_dn" : "<ldap_dn> = None"
224 "ldap_dn" : "<ldap_dn> = None"
184 }
225 }
185
226
186 OUTPUT::
227 OUTPUT::
187
228
188 result: {
229 result: {
189 "id" : "<new_user_id>",
230 "id" : "<new_user_id>",
190 "msg" : "created new user <username>",
231 "msg" : "created new user <username>",
191 "user": {
232 "user": {
192 "id" : "<id>",
233 "id" : "<id>",
193 "username" : "<username>",
234 "username" : "<username>",
194 "firstname": "<firstname>",
235 "firstname": "<firstname>",
195 "lastname" : "<lastname>",
236 "lastname" : "<lastname>",
196 "email" : "<email>",
237 "email" : "<email>",
197 "active" : "<bool>",
238 "active" : "<bool>",
198 "admin" :Β  "<bool>",
239 "admin" :Β  "<bool>",
199 "ldap_dn" : "<ldap_dn>",
240 "ldap_dn" : "<ldap_dn>",
200 "last_login": "<last_login>",
241 "last_login": "<last_login>",
201 },
242 },
202 }
243 }
203 error: null
244 error: null
204
245
205
246
206 update_user
247 update_user
207 -----------
248 -----------
208
249
209 updates given user if such user exists. This command can
250 updates given user if such user exists. This command can
210 be executed only using api_key belonging to user with admin rights.
251 be executed only using api_key belonging to user with admin rights.
211
252
212
253
213 INPUT::
254 INPUT::
214
255
215 id : <id_for_response>
256 id : <id_for_response>
216 api_key : "<api_key>"
257 api_key : "<api_key>"
217 method : "update_user"
258 method : "update_user"
218 args : {
259 args : {
219 "userid" : "<user_id or username>",
260 "userid" : "<user_id or username>",
220 "username" : "<username>",
261 "username" : "<username>",
221 "password" : "<password>",
262 "password" : "<password>",
222 "email" : "<useremail>",
263 "email" : "<useremail>",
223 "firstname" : "<firstname>",
264 "firstname" : "<firstname>",
224 "lastname" : "<lastname>",
265 "lastname" : "<lastname>",
225 "active" : "<bool>",
266 "active" : "<bool>",
226 "admin" : "<bool>",
267 "admin" : "<bool>",
227 "ldap_dn" : "<ldap_dn>"
268 "ldap_dn" : "<ldap_dn>"
228 }
269 }
229
270
230 OUTPUT::
271 OUTPUT::
231
272
232 result: {
273 result: {
233 "id" : "<edited_user_id>",
274 "id" : "<edited_user_id>",
234 "msg" : "updated user ID:<userid> <username>"
275 "msg" : "updated user ID:<userid> <username>"
235 }
276 }
236 error: null
277 error: null
237
278
238
279
239 delete_user
280 delete_user
240 -----------
281 -----------
241
282
242
283
243 deletes givenuser if such user exists. This command can
284 deletes givenuser if such user exists. This command can
244 be executed only using api_key belonging to user with admin rights.
285 be executed only using api_key belonging to user with admin rights.
245
286
246
287
247 INPUT::
288 INPUT::
248
289
249 id : <id_for_response>
290 id : <id_for_response>
250 api_key : "<api_key>"
291 api_key : "<api_key>"
251 method : "delete_user"
292 method : "delete_user"
252 args : {
293 args : {
253 "userid" : "<user_id or username>",
294 "userid" : "<user_id or username>",
254 }
295 }
255
296
256 OUTPUT::
297 OUTPUT::
257
298
258 result: {
299 result: {
259 "id" : "<edited_user_id>",
300 "id" : "<edited_user_id>",
260 "msg" : "deleted user ID:<userid> <username>"
301 "msg" : "deleted user ID:<userid> <username>"
261 }
302 }
262 error: null
303 error: null
263
304
264
305
265 get_users_group
306 get_users_group
266 ---------------
307 ---------------
267
308
268 Gets an existing users group. This command can be executed only using api_key
309 Gets an existing users group. This command can be executed only using api_key
269 belonging to user with admin rights.
310 belonging to user with admin rights.
270
311
271
312
272 INPUT::
313 INPUT::
273
314
274 id : <id_for_response>
315 id : <id_for_response>
275 api_key : "<api_key>"
316 api_key : "<api_key>"
276 method : "get_users_group"
317 method : "get_users_group"
277 args : {
318 args : {
278 "group_name" : "<name>"
319 "group_name" : "<name>"
279 }
320 }
280
321
281 OUTPUT::
322 OUTPUT::
282
323
283 result : None if group not exist
324 result : None if group not exist
284 {
325 {
285 "id" : "<id>",
326 "id" : "<id>",
286 "group_name" : "<groupname>",
327 "group_name" : "<groupname>",
287 "active": "<bool>",
328 "active": "<bool>",
288 "members" : [
329 "members" : [
289 { "id" : "<userid>",
330 { "id" : "<userid>",
290 "username" : "<username>",
331 "username" : "<username>",
291 "firstname": "<firstname>",
332 "firstname": "<firstname>",
292 "lastname" : "<lastname>",
333 "lastname" : "<lastname>",
293 "email" : "<email>",
334 "email" : "<email>",
294 "active" : "<bool>",
335 "active" : "<bool>",
295 "admin" :Β  "<bool>",
336 "admin" :Β  "<bool>",
296 "ldap" : "<ldap_dn>"
337 "ldap" : "<ldap_dn>"
297 },
338 },
298 …
339 …
299 ]
340 ]
300 }
341 }
301 error : null
342 error : null
302
343
303
344
304 get_users_groups
345 get_users_groups
305 ----------------
346 ----------------
306
347
307 Lists all existing users groups. This command can be executed only using
348 Lists all existing users groups. This command can be executed only using
308 api_key belonging to user with admin rights.
349 api_key belonging to user with admin rights.
309
350
310
351
311 INPUT::
352 INPUT::
312
353
313 id : <id_for_response>
354 id : <id_for_response>
314 api_key : "<api_key>"
355 api_key : "<api_key>"
315 method : "get_users_groups"
356 method : "get_users_groups"
316 args : { }
357 args : { }
317
358
318 OUTPUT::
359 OUTPUT::
319
360
320 result : [
361 result : [
321 {
362 {
322 "id" : "<id>",
363 "id" : "<id>",
323 "group_name" : "<groupname>",
364 "group_name" : "<groupname>",
324 "active": "<bool>",
365 "active": "<bool>",
325 "members" : [
366 "members" : [
326 {
367 {
327 "id" : "<userid>",
368 "id" : "<userid>",
328 "username" : "<username>",
369 "username" : "<username>",
329 "firstname": "<firstname>",
370 "firstname": "<firstname>",
330 "lastname" : "<lastname>",
371 "lastname" : "<lastname>",
331 "email" : "<email>",
372 "email" : "<email>",
332 "active" : "<bool>",
373 "active" : "<bool>",
333 "admin" :Β  "<bool>",
374 "admin" :Β  "<bool>",
334 "ldap" : "<ldap_dn>"
375 "ldap" : "<ldap_dn>"
335 },
376 },
336 …
377 …
337 ]
378 ]
338 }
379 }
339 ]
380 ]
340 error : null
381 error : null
341
382
342
383
343 create_users_group
384 create_users_group
344 ------------------
385 ------------------
345
386
346 Creates new users group. This command can be executed only using api_key
387 Creates new users group. This command can be executed only using api_key
347 belonging to user with admin rights
388 belonging to user with admin rights
348
389
349
390
350 INPUT::
391 INPUT::
351
392
352 id : <id_for_response>
393 id : <id_for_response>
353 api_key : "<api_key>"
394 api_key : "<api_key>"
354 method : "create_users_group"
395 method : "create_users_group"
355 args: {
396 args: {
356 "group_name": "<groupname>",
397 "group_name": "<groupname>",
357 "active":"<bool> = True"
398 "active":"<bool> = True"
358 }
399 }
359
400
360 OUTPUT::
401 OUTPUT::
361
402
362 result: {
403 result: {
363 "id": "<newusersgroupid>",
404 "id": "<newusersgroupid>",
364 "msg": "created new users group <groupname>"
405 "msg": "created new users group <groupname>"
365 }
406 }
366 error: null
407 error: null
367
408
368
409
369 add_user_to_users_group
410 add_user_to_users_group
370 -----------------------
411 -----------------------
371
412
372 Adds a user to a users group. If user exists in that group success will be
413 Adds a user to a users group. If user exists in that group success will be
373 `false`. This command can be executed only using api_key
414 `false`. This command can be executed only using api_key
374 belonging to user with admin rights
415 belonging to user with admin rights
375
416
376
417
377 INPUT::
418 INPUT::
378
419
379 id : <id_for_response>
420 id : <id_for_response>
380 api_key : "<api_key>"
421 api_key : "<api_key>"
381 method : "add_user_users_group"
422 method : "add_user_users_group"
382 args: {
423 args: {
383 "group_name" : "<groupname>",
424 "group_name" : "<groupname>",
384 "username" : "<username>"
425 "username" : "<username>"
385 }
426 }
386
427
387 OUTPUT::
428 OUTPUT::
388
429
389 result: {
430 result: {
390 "id": "<newusersgroupmemberid>",
431 "id": "<newusersgroupmemberid>",
391 "success": True|False # depends on if member is in group
432 "success": True|False # depends on if member is in group
392 "msg": "added member <username> to users group <groupname> |
433 "msg": "added member <username> to users group <groupname> |
393 User is already in that group"
434 User is already in that group"
394 }
435 }
395 error: null
436 error: null
396
437
397
438
398 remove_user_from_users_group
439 remove_user_from_users_group
399 ----------------------------
440 ----------------------------
400
441
401 Removes a user from a users group. If user is not in given group success will
442 Removes a user from a users group. If user is not in given group success will
402 be `false`. This command can be executed only
443 be `false`. This command can be executed only
403 using api_key belonging to user with admin rights
444 using api_key belonging to user with admin rights
404
445
405
446
406 INPUT::
447 INPUT::
407
448
408 id : <id_for_response>
449 id : <id_for_response>
409 api_key : "<api_key>"
450 api_key : "<api_key>"
410 method : "remove_user_from_users_group"
451 method : "remove_user_from_users_group"
411 args: {
452 args: {
412 "group_name" : "<groupname>",
453 "group_name" : "<groupname>",
413 "username" : "<username>"
454 "username" : "<username>"
414 }
455 }
415
456
416 OUTPUT::
457 OUTPUT::
417
458
418 result: {
459 result: {
419 "success": True|False, # depends on if member is in group
460 "success": True|False, # depends on if member is in group
420 "msg": "removed member <username> from users group <groupname> |
461 "msg": "removed member <username> from users group <groupname> |
421 User wasn't in group"
462 User wasn't in group"
422 }
463 }
423 error: null
464 error: null
424
465
425
466
426 get_repo
467 get_repo
427 --------
468 --------
428
469
429 Gets an existing repository by it's name or repository_id. Members will return
470 Gets an existing repository by it's name or repository_id. Members will return
430 either users_group or user associated to that repository. This command can
471 either users_group or user associated to that repository. This command can
431 be executed only using api_key belonging to user with admin rights.
472 be executed only using api_key belonging to user with admin rights.
432
473
433
474
434 INPUT::
475 INPUT::
435
476
436 id : <id_for_response>
477 id : <id_for_response>
437 api_key : "<api_key>"
478 api_key : "<api_key>"
438 method : "get_repo"
479 method : "get_repo"
439 args: {
480 args: {
440 "repoid" : "<reponame or repo_id>"
481 "repoid" : "<reponame or repo_id>"
441 }
482 }
442
483
443 OUTPUT::
484 OUTPUT::
444
485
445 result: None if repository does not exist or
486 result: None if repository does not exist or
446 {
487 {
447 "id" : "<id>",
488 "id" : "<id>",
448 "repo_name" : "<reponame>"
489 "repo_name" : "<reponame>"
449 "type" : "<type>",
490 "type" : "<type>",
450 "description" : "<description>",
491 "description" : "<description>",
451 "clone_uri" : "<clone_uri>",
492 "clone_uri" : "<clone_uri>",
452 "private": : "<bool>",
493 "private": : "<bool>",
453 "created_on" : "<datetimecreated>",
494 "created_on" : "<datetimecreated>",
454 "members" : [
495 "members" : [
455 {
496 {
456 "type": "user",
497 "type": "user",
457 "id" : "<userid>",
498 "id" : "<userid>",
458 "username" : "<username>",
499 "username" : "<username>",
459 "firstname": "<firstname>",
500 "firstname": "<firstname>",
460 "lastname" : "<lastname>",
501 "lastname" : "<lastname>",
461 "email" : "<email>",
502 "email" : "<email>",
462 "active" : "<bool>",
503 "active" : "<bool>",
463 "admin" :Β  "<bool>",
504 "admin" :Β  "<bool>",
464 "ldap" : "<ldap_dn>",
505 "ldap" : "<ldap_dn>",
465 "permission" : "repository.(read|write|admin)"
506 "permission" : "repository.(read|write|admin)"
466 },
507 },
467 …
508 …
468 {
509 {
469 "type": "users_group",
510 "type": "users_group",
470 "id" : "<usersgroupid>",
511 "id" : "<usersgroupid>",
471 "name" : "<usersgroupname>",
512 "name" : "<usersgroupname>",
472 "active": "<bool>",
513 "active": "<bool>",
473 "permission" : "repository.(read|write|admin)"
514 "permission" : "repository.(read|write|admin)"
474 },
515 },
475 …
516 …
476 ]
517 ]
477 }
518 }
478 error: null
519 error: null
479
520
480
521
481 get_repos
522 get_repos
482 ---------
523 ---------
483
524
484 Lists all existing repositories. This command can be executed only using api_key
525 Lists all existing repositories. This command can be executed only using api_key
485 belonging to user with admin rights
526 belonging to user with admin rights
486
527
487
528
488 INPUT::
529 INPUT::
489
530
490 id : <id_for_response>
531 id : <id_for_response>
491 api_key : "<api_key>"
532 api_key : "<api_key>"
492 method : "get_repos"
533 method : "get_repos"
493 args: { }
534 args: { }
494
535
495 OUTPUT::
536 OUTPUT::
496
537
497 result: [
538 result: [
498 {
539 {
499 "id" : "<id>",
540 "id" : "<id>",
500 "repo_name" : "<reponame>"
541 "repo_name" : "<reponame>"
501 "type" : "<type>",
542 "type" : "<type>",
502 "description" : "<description>",
543 "description" : "<description>",
503 "clone_uri" : "<clone_uri>",
544 "clone_uri" : "<clone_uri>",
504 "private": : "<bool>",
545 "private": : "<bool>",
505 "created_on" : "<datetimecreated>",
546 "created_on" : "<datetimecreated>",
506 },
547 },
507 …
548 …
508 ]
549 ]
509 error: null
550 error: null
510
551
511
552
512 get_repo_nodes
553 get_repo_nodes
513 --------------
554 --------------
514
555
515 returns a list of nodes and it's children in a flat list for a given path
556 returns a list of nodes and it's children in a flat list for a given path
516 at given revision. It's possible to specify ret_type to show only `files` or
557 at given revision. It's possible to specify ret_type to show only `files` or
517 `dirs`. This command can be executed only using api_key belonging to user
558 `dirs`. This command can be executed only using api_key belonging to user
518 with admin rights
559 with admin rights
519
560
520
561
521 INPUT::
562 INPUT::
522
563
523 id : <id_for_response>
564 id : <id_for_response>
524 api_key : "<api_key>"
565 api_key : "<api_key>"
525 method : "get_repo_nodes"
566 method : "get_repo_nodes"
526 args: {
567 args: {
527 "repo_name" : "<reponame>",
568 "repo_name" : "<reponame>",
528 "revision" : "<revision>",
569 "revision" : "<revision>",
529 "root_path" : "<root_path>",
570 "root_path" : "<root_path>",
530 "ret_type" : "<ret_type>" = 'all'
571 "ret_type" : "<ret_type>" = 'all'
531 }
572 }
532
573
533 OUTPUT::
574 OUTPUT::
534
575
535 result: [
576 result: [
536 {
577 {
537 "name" : "<name>"
578 "name" : "<name>"
538 "type" : "<type>",
579 "type" : "<type>",
539 },
580 },
540 …
581 …
541 ]
582 ]
542 error: null
583 error: null
543
584
544
585
545 create_repo
586 create_repo
546 -----------
587 -----------
547
588
548 Creates a repository. This command can be executed only using api_key
589 Creates a repository. This command can be executed only using api_key
549 belonging to user with admin rights.
590 belonging to user with admin rights.
550 If repository name contains "/", all needed repository groups will be created.
591 If repository name contains "/", all needed repository groups will be created.
551 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
592 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
552 and create "baz" repository with "bar" as group.
593 and create "baz" repository with "bar" as group.
553
594
554
595
555 INPUT::
596 INPUT::
556
597
557 id : <id_for_response>
598 id : <id_for_response>
558 api_key : "<api_key>"
599 api_key : "<api_key>"
559 method : "create_repo"
600 method : "create_repo"
560 args: {
601 args: {
561 "repo_name" : "<reponame>",
602 "repo_name" : "<reponame>",
562 "owner_name" : "<ownername>",
603 "owner_name" : "<ownername>",
563 "description" : "<description> = ''",
604 "description" : "<description> = ''",
564 "repo_type" : "<type> = 'hg'",
605 "repo_type" : "<type> = 'hg'",
565 "private" : "<bool> = False",
606 "private" : "<bool> = False",
566 "clone_uri" : "<clone_uri> = None",
607 "clone_uri" : "<clone_uri> = None",
567 }
608 }
568
609
569 OUTPUT::
610 OUTPUT::
570
611
571 result: {
612 result: {
572 "id": "<newrepoid>",
613 "id": "<newrepoid>",
573 "msg": "Created new repository <reponame>",
614 "msg": "Created new repository <reponame>",
574 "repo": {
615 "repo": {
575 "id" : "<id>",
616 "id" : "<id>",
576 "repo_name" : "<reponame>"
617 "repo_name" : "<reponame>"
577 "type" : "<type>",
618 "type" : "<type>",
578 "description" : "<description>",
619 "description" : "<description>",
579 "clone_uri" : "<clone_uri>",
620 "clone_uri" : "<clone_uri>",
580 "private": : "<bool>",
621 "private": : "<bool>",
581 "created_on" : "<datetimecreated>",
622 "created_on" : "<datetimecreated>",
582 },
623 },
583 }
624 }
584 error: null
625 error: null
585
626
586
627
587 delete_repo
628 delete_repo
588 -----------
629 -----------
589
630
590 Deletes a repository. This command can be executed only using api_key
631 Deletes a repository. This command can be executed only using api_key
591 belonging to user with admin rights.
632 belonging to user with admin rights.
592
633
593
634
594 INPUT::
635 INPUT::
595
636
596 id : <id_for_response>
637 id : <id_for_response>
597 api_key : "<api_key>"
638 api_key : "<api_key>"
598 method : "delete_repo"
639 method : "delete_repo"
599 args: {
640 args: {
600 "repo_name" : "<reponame>",
641 "repo_name" : "<reponame>",
601 }
642 }
602
643
603 OUTPUT::
644 OUTPUT::
604
645
605 result: {
646 result: {
606 "msg": "Deleted repository <reponame>",
647 "msg": "Deleted repository <reponame>",
607 }
648 }
608 error: null
649 error: null
609
650
610
651
611 grant_user_permission
652 grant_user_permission
612 ---------------------
653 ---------------------
613
654
614 Grant permission for user on given repository, or update existing one
655 Grant permission for user on given repository, or update existing one
615 if found. This command can be executed only using api_key belonging to user
656 if found. This command can be executed only using api_key belonging to user
616 with admin rights.
657 with admin rights.
617
658
618
659
619 INPUT::
660 INPUT::
620
661
621 id : <id_for_response>
662 id : <id_for_response>
622 api_key : "<api_key>"
663 api_key : "<api_key>"
623 method : "grant_user_permission"
664 method : "grant_user_permission"
624 args: {
665 args: {
625 "repo_name" : "<reponame>",
666 "repo_name" : "<reponame>",
626 "username" : "<username>",
667 "username" : "<username>",
627 "perm" : "(repository.(none|read|write|admin))",
668 "perm" : "(repository.(none|read|write|admin))",
628 }
669 }
629
670
630 OUTPUT::
671 OUTPUT::
631
672
632 result: {
673 result: {
633 "msg" : "Granted perm: <perm> for user: <username> in repo: <reponame>"
674 "msg" : "Granted perm: <perm> for user: <username> in repo: <reponame>"
634 }
675 }
635 error: null
676 error: null
636
677
637
678
638 revoke_user_permission
679 revoke_user_permission
639 ----------------------
680 ----------------------
640
681
641 Revoke permission for user on given repository. This command can be executed
682 Revoke permission for user on given repository. This command can be executed
642 only using api_key belonging to user with admin rights.
683 only using api_key belonging to user with admin rights.
643
684
644
685
645 INPUT::
686 INPUT::
646
687
647 id : <id_for_response>
688 id : <id_for_response>
648 api_key : "<api_key>"
689 api_key : "<api_key>"
649 method : "revoke_user_permission"
690 method : "revoke_user_permission"
650 args: {
691 args: {
651 "repo_name" : "<reponame>",
692 "repo_name" : "<reponame>",
652 "username" : "<username>",
693 "username" : "<username>",
653 }
694 }
654
695
655 OUTPUT::
696 OUTPUT::
656
697
657 result: {
698 result: {
658 "msg" : "Revoked perm for user: <suername> in repo: <reponame>"
699 "msg" : "Revoked perm for user: <suername> in repo: <reponame>"
659 }
700 }
660 error: null
701 error: null
661
702
662
703
663 grant_users_group_permission
704 grant_users_group_permission
664 ----------------------------
705 ----------------------------
665
706
666 Grant permission for users group on given repository, or update
707 Grant permission for users group on given repository, or update
667 existing one if found. This command can be executed only using
708 existing one if found. This command can be executed only using
668 api_key belonging to user with admin rights.
709 api_key belonging to user with admin rights.
669
710
670
711
671 INPUT::
712 INPUT::
672
713
673 id : <id_for_response>
714 id : <id_for_response>
674 api_key : "<api_key>"
715 api_key : "<api_key>"
675 method : "grant_users_group_permission"
716 method : "grant_users_group_permission"
676 args: {
717 args: {
677 "repo_name" : "<reponame>",
718 "repo_name" : "<reponame>",
678 "group_name" : "<usersgroupname>",
719 "group_name" : "<usersgroupname>",
679 "perm" : "(repository.(none|read|write|admin))",
720 "perm" : "(repository.(none|read|write|admin))",
680 }
721 }
681
722
682 OUTPUT::
723 OUTPUT::
683
724
684 result: {
725 result: {
685 "msg" : "Granted perm: <perm> for group: <usersgroupname> in repo: <reponame>"
726 "msg" : "Granted perm: <perm> for group: <usersgroupname> in repo: <reponame>"
686 }
727 }
687 error: null
728 error: null
688
729
689
730
690 revoke_users_group_permission
731 revoke_users_group_permission
691 -----------------------------
732 -----------------------------
692
733
693 Revoke permission for users group on given repository.This command can be
734 Revoke permission for users group on given repository.This command can be
694 executed only using api_key belonging to user with admin rights.
735 executed only using api_key belonging to user with admin rights.
695
736
696 INPUT::
737 INPUT::
697
738
698 id : <id_for_response>
739 id : <id_for_response>
699 api_key : "<api_key>"
740 api_key : "<api_key>"
700 method : "revoke_users_group_permission"
741 method : "revoke_users_group_permission"
701 args: {
742 args: {
702 "repo_name" : "<reponame>",
743 "repo_name" : "<reponame>",
703 "users_group" : "<usersgroupname>",
744 "users_group" : "<usersgroupname>",
704 }
745 }
705
746
706 OUTPUT::
747 OUTPUT::
707
748
708 result: {
749 result: {
709 "msg" : "Revoked perm for group: <usersgroupname> in repo: <reponame>"
750 "msg" : "Revoked perm for group: <usersgroupname> in repo: <reponame>"
710 }
751 }
711 error: null No newline at end of file
752 error: null
@@ -1,687 +1,688 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7 1.4.0 (**2012-XX-XX**)
7 1.4.0 (**2012-XX-XX**)
8 ----------------------
8 ----------------------
9
9
10 :status: in-progress
10 :status: in-progress
11 :branch: beta
11 :branch: beta
12
12
13 news
13 news
14 ++++
14 ++++
15
15
16 - new codereview system
16 - new codereview system
17 - email map, allowing users to have multiple email addresses mapped into
17 - email map, allowing users to have multiple email addresses mapped into
18 their accounts
18 their accounts
19 - changed setup-app into setup-rhodecode and added default options to it.
19 - changed setup-app into setup-rhodecode and added default options to it.
20 - new git repos are created as bare now by default
20 - new git repos are created as bare now by default
21 - #464 added links to groups in permission box
21 - #464 added links to groups in permission box
22 - #465 mentions autocomplete inside comments boxes
22 - #465 mentions autocomplete inside comments boxes
23 - #469 added --update-only option to whoosh to re-index only given list
23 - #469 added --update-only option to whoosh to re-index only given list
24 of repos in index
24 of repos in index
25 - rhodecode-api CLI client
25
26
26 fixes
27 fixes
27 +++++
28 +++++
28
29
29 - improved translations
30 - improved translations
30 - fixes issue #455 Creating an archive generates an exception on Windows
31 - fixes issue #455 Creating an archive generates an exception on Windows
31 - fixes #448 Download ZIP archive keeps file in /tmp open and results
32 - fixes #448 Download ZIP archive keeps file in /tmp open and results
32 in out of disk space
33 in out of disk space
33 - fixes issue #454 Search results under Windows include proceeding
34 - fixes issue #454 Search results under Windows include proceeding
34 backslash
35 backslash
35 - fixed issue #450. Rhodecode no longer will crash when bad revision is
36 - fixed issue #450. Rhodecode no longer will crash when bad revision is
36 present in journal data.
37 present in journal data.
37 - fix for issue #417, git execution was broken on windows for certain
38 - fix for issue #417, git execution was broken on windows for certain
38 commands.
39 commands.
39 - fixed #413. Don't disable .git directory for bare repos on deleting
40 - fixed #413. Don't disable .git directory for bare repos on deleting
40 - fixed issue #459. Changed the way of obtaining logger in reindex task.
41 - fixed issue #459. Changed the way of obtaining logger in reindex task.
41
42
42 1.3.6 (**2012-05-17**)
43 1.3.6 (**2012-05-17**)
43 ----------------------
44 ----------------------
44
45
45 news
46 news
46 ++++
47 ++++
47
48
48 - chinese traditional translation
49 - chinese traditional translation
49 - changed setup-app into setup-rhodecode and added arguments for auto-setup
50 - changed setup-app into setup-rhodecode and added arguments for auto-setup
50 mode that doesn't need user interaction
51 mode that doesn't need user interaction
51
52
52 fixes
53 fixes
53 +++++
54 +++++
54
55
55 - fixed no scm found warning
56 - fixed no scm found warning
56 - fixed __future__ import error on rcextensions
57 - fixed __future__ import error on rcextensions
57 - made simplejson required lib for speedup on JSON encoding
58 - made simplejson required lib for speedup on JSON encoding
58 - fixes #449 bad regex could get more than revisions from parsing history
59 - fixes #449 bad regex could get more than revisions from parsing history
59 - don't clear DB session when CELERY_EAGER is turned ON
60 - don't clear DB session when CELERY_EAGER is turned ON
60
61
61 1.3.5 (**2012-05-10**)
62 1.3.5 (**2012-05-10**)
62 ----------------------
63 ----------------------
63
64
64 news
65 news
65 ++++
66 ++++
66
67
67 - use ext_json for json module
68 - use ext_json for json module
68 - unified annotation view with file source view
69 - unified annotation view with file source view
69 - notification improvements, better inbox + css
70 - notification improvements, better inbox + css
70 - #419 don't strip passwords for login forms, make rhodecode
71 - #419 don't strip passwords for login forms, make rhodecode
71 more compatible with LDAP servers
72 more compatible with LDAP servers
72 - Added HTTP_X_FORWARDED_FOR as another method of extracting
73 - Added HTTP_X_FORWARDED_FOR as another method of extracting
73 IP for pull/push logs. - moved all to base controller
74 IP for pull/push logs. - moved all to base controller
74 - #415: Adding comment to changeset causes reload.
75 - #415: Adding comment to changeset causes reload.
75 Comments are now added via ajax and doesn't reload the page
76 Comments are now added via ajax and doesn't reload the page
76 - #374 LDAP config is discarded when LDAP can't be activated
77 - #374 LDAP config is discarded when LDAP can't be activated
77 - limited push/pull operations are now logged for git in the journal
78 - limited push/pull operations are now logged for git in the journal
78 - bumped mercurial to 2.2.X series
79 - bumped mercurial to 2.2.X series
79 - added support for displaying submodules in file-browser
80 - added support for displaying submodules in file-browser
80 - #421 added bookmarks in changelog view
81 - #421 added bookmarks in changelog view
81
82
82 fixes
83 fixes
83 +++++
84 +++++
84
85
85 - fixed dev-version marker for stable when served from source codes
86 - fixed dev-version marker for stable when served from source codes
86 - fixed missing permission checks on show forks page
87 - fixed missing permission checks on show forks page
87 - #418 cast to unicode fixes in notification objects
88 - #418 cast to unicode fixes in notification objects
88 - #426 fixed mention extracting regex
89 - #426 fixed mention extracting regex
89 - fixed remote-pulling for git remotes remopositories
90 - fixed remote-pulling for git remotes remopositories
90 - fixed #434: Error when accessing files or changesets of a git repository
91 - fixed #434: Error when accessing files or changesets of a git repository
91 with submodules
92 with submodules
92 - fixed issue with empty APIKEYS for users after registration ref. #438
93 - fixed issue with empty APIKEYS for users after registration ref. #438
93 - fixed issue with getting README files from git repositories
94 - fixed issue with getting README files from git repositories
94
95
95 1.3.4 (**2012-03-28**)
96 1.3.4 (**2012-03-28**)
96 ----------------------
97 ----------------------
97
98
98 news
99 news
99 ++++
100 ++++
100
101
101 - Whoosh logging is now controlled by the .ini files logging setup
102 - Whoosh logging is now controlled by the .ini files logging setup
102 - added clone-url into edit form on /settings page
103 - added clone-url into edit form on /settings page
103 - added help text into repo add/edit forms
104 - added help text into repo add/edit forms
104 - created rcextensions module with additional mappings (ref #322) and
105 - created rcextensions module with additional mappings (ref #322) and
105 post push/pull/create repo hooks callbacks
106 post push/pull/create repo hooks callbacks
106 - implemented #377 Users view for his own permissions on account page
107 - implemented #377 Users view for his own permissions on account page
107 - #399 added inheritance of permissions for users group on repos groups
108 - #399 added inheritance of permissions for users group on repos groups
108 - #401 repository group is automatically pre-selected when adding repos
109 - #401 repository group is automatically pre-selected when adding repos
109 inside a repository group
110 inside a repository group
110 - added alternative HTTP 403 response when client failed to authenticate. Helps
111 - added alternative HTTP 403 response when client failed to authenticate. Helps
111 solving issues with Mercurial and LDAP
112 solving issues with Mercurial and LDAP
112 - #402 removed group prefix from repository name when listing repositories
113 - #402 removed group prefix from repository name when listing repositories
113 inside a group
114 inside a group
114 - added gravatars into permission view and permissions autocomplete
115 - added gravatars into permission view and permissions autocomplete
115 - #347 when running multiple RhodeCode instances, properly invalidates cache
116 - #347 when running multiple RhodeCode instances, properly invalidates cache
116 for all registered servers
117 for all registered servers
117
118
118 fixes
119 fixes
119 +++++
120 +++++
120
121
121 - fixed #390 cache invalidation problems on repos inside group
122 - fixed #390 cache invalidation problems on repos inside group
122 - fixed #385 clone by ID url was loosing proxy prefix in URL
123 - fixed #385 clone by ID url was loosing proxy prefix in URL
123 - fixed some unicode problems with waitress
124 - fixed some unicode problems with waitress
124 - fixed issue with escaping < and > in changeset commits
125 - fixed issue with escaping < and > in changeset commits
125 - fixed error occurring during recursive group creation in API
126 - fixed error occurring during recursive group creation in API
126 create_repo function
127 create_repo function
127 - fixed #393 py2.5 fixes for routes url generator
128 - fixed #393 py2.5 fixes for routes url generator
128 - fixed #397 Private repository groups shows up before login
129 - fixed #397 Private repository groups shows up before login
129 - fixed #396 fixed problems with revoking users in nested groups
130 - fixed #396 fixed problems with revoking users in nested groups
130 - fixed mysql unicode issues + specified InnoDB as default engine with
131 - fixed mysql unicode issues + specified InnoDB as default engine with
131 utf8 charset
132 utf8 charset
132 - #406 trim long branch/tag names in changelog to not break UI
133 - #406 trim long branch/tag names in changelog to not break UI
133
134
134 1.3.3 (**2012-03-02**)
135 1.3.3 (**2012-03-02**)
135 ----------------------
136 ----------------------
136
137
137 news
138 news
138 ++++
139 ++++
139
140
140
141
141 fixes
142 fixes
142 +++++
143 +++++
143
144
144 - fixed some python2.5 compatibility issues
145 - fixed some python2.5 compatibility issues
145 - fixed issues with removed repos was accidentally added as groups, after
146 - fixed issues with removed repos was accidentally added as groups, after
146 full rescan of paths
147 full rescan of paths
147 - fixes #376 Cannot edit user (using container auth)
148 - fixes #376 Cannot edit user (using container auth)
148 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
149 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
149 configuration
150 configuration
150 - fixed initial sorting of repos inside repo group
151 - fixed initial sorting of repos inside repo group
151 - fixes issue when user tried to resubmit same permission into user/user_groups
152 - fixes issue when user tried to resubmit same permission into user/user_groups
152 - bumped beaker version that fixes #375 leap error bug
153 - bumped beaker version that fixes #375 leap error bug
153 - fixed raw_changeset for git. It was generated with hg patch headers
154 - fixed raw_changeset for git. It was generated with hg patch headers
154 - fixed vcs issue with last_changeset for filenodes
155 - fixed vcs issue with last_changeset for filenodes
155 - fixed missing commit after hook delete
156 - fixed missing commit after hook delete
156 - fixed #372 issues with git operation detection that caused a security issue
157 - fixed #372 issues with git operation detection that caused a security issue
157 for git repos
158 for git repos
158
159
159 1.3.2 (**2012-02-28**)
160 1.3.2 (**2012-02-28**)
160 ----------------------
161 ----------------------
161
162
162 news
163 news
163 ++++
164 ++++
164
165
165
166
166 fixes
167 fixes
167 +++++
168 +++++
168
169
169 - fixed git protocol issues with repos-groups
170 - fixed git protocol issues with repos-groups
170 - fixed git remote repos validator that prevented from cloning remote git repos
171 - fixed git remote repos validator that prevented from cloning remote git repos
171 - fixes #370 ending slashes fixes for repo and groups
172 - fixes #370 ending slashes fixes for repo and groups
172 - fixes #368 improved git-protocol detection to handle other clients
173 - fixes #368 improved git-protocol detection to handle other clients
173 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
174 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
174 Moved To Root
175 Moved To Root
175 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
176 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
176 - fixed #373 missing cascade drop on user_group_to_perm table
177 - fixed #373 missing cascade drop on user_group_to_perm table
177
178
178 1.3.1 (**2012-02-27**)
179 1.3.1 (**2012-02-27**)
179 ----------------------
180 ----------------------
180
181
181 news
182 news
182 ++++
183 ++++
183
184
184
185
185 fixes
186 fixes
186 +++++
187 +++++
187
188
188 - redirection loop occurs when remember-me wasn't checked during login
189 - redirection loop occurs when remember-me wasn't checked during login
189 - fixes issues with git blob history generation
190 - fixes issues with git blob history generation
190 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
191 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
191
192
192 1.3.0 (**2012-02-26**)
193 1.3.0 (**2012-02-26**)
193 ----------------------
194 ----------------------
194
195
195 news
196 news
196 ++++
197 ++++
197
198
198 - code review, inspired by github code-comments
199 - code review, inspired by github code-comments
199 - #215 rst and markdown README files support
200 - #215 rst and markdown README files support
200 - #252 Container-based and proxy pass-through authentication support
201 - #252 Container-based and proxy pass-through authentication support
201 - #44 branch browser. Filtering of changelog by branches
202 - #44 branch browser. Filtering of changelog by branches
202 - mercurial bookmarks support
203 - mercurial bookmarks support
203 - new hover top menu, optimized to add maximum size for important views
204 - new hover top menu, optimized to add maximum size for important views
204 - configurable clone url template with possibility to specify protocol like
205 - configurable clone url template with possibility to specify protocol like
205 ssh:// or http:// and also manually alter other parts of clone_url.
206 ssh:// or http:// and also manually alter other parts of clone_url.
206 - enabled largefiles extension by default
207 - enabled largefiles extension by default
207 - optimized summary file pages and saved a lot of unused space in them
208 - optimized summary file pages and saved a lot of unused space in them
208 - #239 option to manually mark repository as fork
209 - #239 option to manually mark repository as fork
209 - #320 mapping of commit authors to RhodeCode users
210 - #320 mapping of commit authors to RhodeCode users
210 - #304 hashes are displayed using monospace font
211 - #304 hashes are displayed using monospace font
211 - diff configuration, toggle white lines and context lines
212 - diff configuration, toggle white lines and context lines
212 - #307 configurable diffs, whitespace toggle, increasing context lines
213 - #307 configurable diffs, whitespace toggle, increasing context lines
213 - sorting on branches, tags and bookmarks using YUI datatable
214 - sorting on branches, tags and bookmarks using YUI datatable
214 - improved file filter on files page
215 - improved file filter on files page
215 - implements #330 api method for listing nodes ar particular revision
216 - implements #330 api method for listing nodes ar particular revision
216 - #73 added linking issues in commit messages to chosen issue tracker url
217 - #73 added linking issues in commit messages to chosen issue tracker url
217 based on user defined regular expression
218 based on user defined regular expression
218 - added linking of changesets in commit messages
219 - added linking of changesets in commit messages
219 - new compact changelog with expandable commit messages
220 - new compact changelog with expandable commit messages
220 - firstname and lastname are optional in user creation
221 - firstname and lastname are optional in user creation
221 - #348 added post-create repository hook
222 - #348 added post-create repository hook
222 - #212 global encoding settings is now configurable from .ini files
223 - #212 global encoding settings is now configurable from .ini files
223 - #227 added repository groups permissions
224 - #227 added repository groups permissions
224 - markdown gets codehilite extensions
225 - markdown gets codehilite extensions
225 - new API methods, delete_repositories, grante/revoke permissions for groups
226 - new API methods, delete_repositories, grante/revoke permissions for groups
226 and repos
227 and repos
227
228
228
229
229 fixes
230 fixes
230 +++++
231 +++++
231
232
232 - rewrote dbsession management for atomic operations, and better error handling
233 - rewrote dbsession management for atomic operations, and better error handling
233 - fixed sorting of repo tables
234 - fixed sorting of repo tables
234 - #326 escape of special html entities in diffs
235 - #326 escape of special html entities in diffs
235 - normalized user_name => username in api attributes
236 - normalized user_name => username in api attributes
236 - fixes #298 ldap created users with mixed case emails created conflicts
237 - fixes #298 ldap created users with mixed case emails created conflicts
237 on saving a form
238 on saving a form
238 - fixes issue when owner of a repo couldn't revoke permissions for users
239 - fixes issue when owner of a repo couldn't revoke permissions for users
239 and groups
240 and groups
240 - fixes #271 rare JSON serialization problem with statistics
241 - fixes #271 rare JSON serialization problem with statistics
241 - fixes #337 missing validation check for conflicting names of a group with a
242 - fixes #337 missing validation check for conflicting names of a group with a
242 repositories group
243 repositories group
243 - #340 fixed session problem for mysql and celery tasks
244 - #340 fixed session problem for mysql and celery tasks
244 - fixed #331 RhodeCode mangles repository names if the a repository group
245 - fixed #331 RhodeCode mangles repository names if the a repository group
245 contains the "full path" to the repositories
246 contains the "full path" to the repositories
246 - #355 RhodeCode doesn't store encrypted LDAP passwords
247 - #355 RhodeCode doesn't store encrypted LDAP passwords
247
248
248 1.2.5 (**2012-01-28**)
249 1.2.5 (**2012-01-28**)
249 ----------------------
250 ----------------------
250
251
251 news
252 news
252 ++++
253 ++++
253
254
254 fixes
255 fixes
255 +++++
256 +++++
256
257
257 - #340 Celery complains about MySQL server gone away, added session cleanup
258 - #340 Celery complains about MySQL server gone away, added session cleanup
258 for celery tasks
259 for celery tasks
259 - #341 "scanning for repositories in None" log message during Rescan was missing
260 - #341 "scanning for repositories in None" log message during Rescan was missing
260 a parameter
261 a parameter
261 - fixed creating archives with subrepos. Some hooks were triggered during that
262 - fixed creating archives with subrepos. Some hooks were triggered during that
262 operation leading to crash.
263 operation leading to crash.
263 - fixed missing email in account page.
264 - fixed missing email in account page.
264 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
265 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
265 forking on windows impossible
266 forking on windows impossible
266
267
267 1.2.4 (**2012-01-19**)
268 1.2.4 (**2012-01-19**)
268 ----------------------
269 ----------------------
269
270
270 news
271 news
271 ++++
272 ++++
272
273
273 - RhodeCode is bundled with mercurial series 2.0.X by default, with
274 - RhodeCode is bundled with mercurial series 2.0.X by default, with
274 full support to largefiles extension. Enabled by default in new installations
275 full support to largefiles extension. Enabled by default in new installations
275 - #329 Ability to Add/Remove Groups to/from a Repository via AP
276 - #329 Ability to Add/Remove Groups to/from a Repository via AP
276 - added requires.txt file with requirements
277 - added requires.txt file with requirements
277
278
278 fixes
279 fixes
279 +++++
280 +++++
280
281
281 - fixes db session issues with celery when emailing admins
282 - fixes db session issues with celery when emailing admins
282 - #331 RhodeCode mangles repository names if the a repository group
283 - #331 RhodeCode mangles repository names if the a repository group
283 contains the "full path" to the repositories
284 contains the "full path" to the repositories
284 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
285 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
285 - DB session cleanup after hg protocol operations, fixes issues with
286 - DB session cleanup after hg protocol operations, fixes issues with
286 `mysql has gone away` errors
287 `mysql has gone away` errors
287 - #333 doc fixes for get_repo api function
288 - #333 doc fixes for get_repo api function
288 - #271 rare JSON serialization problem with statistics enabled
289 - #271 rare JSON serialization problem with statistics enabled
289 - #337 Fixes issues with validation of repository name conflicting with
290 - #337 Fixes issues with validation of repository name conflicting with
290 a group name. A proper message is now displayed.
291 a group name. A proper message is now displayed.
291 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
292 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
292 doesn't work
293 doesn't work
293 - #316 fixes issues with web description in hgrc files
294 - #316 fixes issues with web description in hgrc files
294
295
295 1.2.3 (**2011-11-02**)
296 1.2.3 (**2011-11-02**)
296 ----------------------
297 ----------------------
297
298
298 news
299 news
299 ++++
300 ++++
300
301
301 - added option to manage repos group for non admin users
302 - added option to manage repos group for non admin users
302 - added following API methods for get_users, create_user, get_users_groups,
303 - added following API methods for get_users, create_user, get_users_groups,
303 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
304 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
304 get_repo, create_repo, add_user_to_repo
305 get_repo, create_repo, add_user_to_repo
305 - implements #237 added password confirmation for my account
306 - implements #237 added password confirmation for my account
306 and admin edit user.
307 and admin edit user.
307 - implements #291 email notification for global events are now sent to all
308 - implements #291 email notification for global events are now sent to all
308 administrator users, and global config email.
309 administrator users, and global config email.
309
310
310 fixes
311 fixes
311 +++++
312 +++++
312
313
313 - added option for passing auth method for smtp mailer
314 - added option for passing auth method for smtp mailer
314 - #276 issue with adding a single user with id>10 to usergroups
315 - #276 issue with adding a single user with id>10 to usergroups
315 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
316 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
316 - #288 fixes managing of repos in a group for non admin user
317 - #288 fixes managing of repos in a group for non admin user
317
318
318 1.2.2 (**2011-10-17**)
319 1.2.2 (**2011-10-17**)
319 ----------------------
320 ----------------------
320
321
321 news
322 news
322 ++++
323 ++++
323
324
324 - #226 repo groups are available by path instead of numerical id
325 - #226 repo groups are available by path instead of numerical id
325
326
326 fixes
327 fixes
327 +++++
328 +++++
328
329
329 - #259 Groups with the same name but with different parent group
330 - #259 Groups with the same name but with different parent group
330 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
331 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
331 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
332 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
332 - #265 ldap save fails sometimes on converting attributes to booleans,
333 - #265 ldap save fails sometimes on converting attributes to booleans,
333 added getter and setter into model that will prevent from this on db model level
334 added getter and setter into model that will prevent from this on db model level
334 - fixed problems with timestamps issues #251 and #213
335 - fixed problems with timestamps issues #251 and #213
335 - fixes #266 RhodeCode allows to create repo with the same name and in
336 - fixes #266 RhodeCode allows to create repo with the same name and in
336 the same parent as group
337 the same parent as group
337 - fixes #245 Rescan of the repositories on Windows
338 - fixes #245 Rescan of the repositories on Windows
338 - fixes #248 cannot edit repos inside a group on windows
339 - fixes #248 cannot edit repos inside a group on windows
339 - fixes #219 forking problems on windows
340 - fixes #219 forking problems on windows
340
341
341 1.2.1 (**2011-10-08**)
342 1.2.1 (**2011-10-08**)
342 ----------------------
343 ----------------------
343
344
344 news
345 news
345 ++++
346 ++++
346
347
347
348
348 fixes
349 fixes
349 +++++
350 +++++
350
351
351 - fixed problems with basic auth and push problems
352 - fixed problems with basic auth and push problems
352 - gui fixes
353 - gui fixes
353 - fixed logger
354 - fixed logger
354
355
355 1.2.0 (**2011-10-07**)
356 1.2.0 (**2011-10-07**)
356 ----------------------
357 ----------------------
357
358
358 news
359 news
359 ++++
360 ++++
360
361
361 - implemented #47 repository groups
362 - implemented #47 repository groups
362 - implemented #89 Can setup google analytics code from settings menu
363 - implemented #89 Can setup google analytics code from settings menu
363 - implemented #91 added nicer looking archive urls with more download options
364 - implemented #91 added nicer looking archive urls with more download options
364 like tags, branches
365 like tags, branches
365 - implemented #44 into file browsing, and added follow branch option
366 - implemented #44 into file browsing, and added follow branch option
366 - implemented #84 downloads can be enabled/disabled for each repository
367 - implemented #84 downloads can be enabled/disabled for each repository
367 - anonymous repository can be cloned without having to pass default:default
368 - anonymous repository can be cloned without having to pass default:default
368 into clone url
369 into clone url
369 - fixed #90 whoosh indexer can index chooses repositories passed in command
370 - fixed #90 whoosh indexer can index chooses repositories passed in command
370 line
371 line
371 - extended journal with day aggregates and paging
372 - extended journal with day aggregates and paging
372 - implemented #107 source code lines highlight ranges
373 - implemented #107 source code lines highlight ranges
373 - implemented #93 customizable changelog on combined revision ranges -
374 - implemented #93 customizable changelog on combined revision ranges -
374 equivalent of githubs compare view
375 equivalent of githubs compare view
375 - implemented #108 extended and more powerful LDAP configuration
376 - implemented #108 extended and more powerful LDAP configuration
376 - implemented #56 users groups
377 - implemented #56 users groups
377 - major code rewrites optimized codes for speed and memory usage
378 - major code rewrites optimized codes for speed and memory usage
378 - raw and diff downloads are now in git format
379 - raw and diff downloads are now in git format
379 - setup command checks for write access to given path
380 - setup command checks for write access to given path
380 - fixed many issues with international characters and unicode. It uses utf8
381 - fixed many issues with international characters and unicode. It uses utf8
381 decode with replace to provide less errors even with non utf8 encoded strings
382 decode with replace to provide less errors even with non utf8 encoded strings
382 - #125 added API KEY access to feeds
383 - #125 added API KEY access to feeds
383 - #109 Repository can be created from external Mercurial link (aka. remote
384 - #109 Repository can be created from external Mercurial link (aka. remote
384 repository, and manually updated (via pull) from admin panel
385 repository, and manually updated (via pull) from admin panel
385 - beta git support - push/pull server + basic view for git repos
386 - beta git support - push/pull server + basic view for git repos
386 - added followers page and forks page
387 - added followers page and forks page
387 - server side file creation (with binary file upload interface)
388 - server side file creation (with binary file upload interface)
388 and edition with commits powered by codemirror
389 and edition with commits powered by codemirror
389 - #111 file browser file finder, quick lookup files on whole file tree
390 - #111 file browser file finder, quick lookup files on whole file tree
390 - added quick login sliding menu into main page
391 - added quick login sliding menu into main page
391 - changelog uses lazy loading of affected files details, in some scenarios
392 - changelog uses lazy loading of affected files details, in some scenarios
392 this can improve speed of changelog page dramatically especially for
393 this can improve speed of changelog page dramatically especially for
393 larger repositories.
394 larger repositories.
394 - implements #214 added support for downloading subrepos in download menu.
395 - implements #214 added support for downloading subrepos in download menu.
395 - Added basic API for direct operations on rhodecode via JSON
396 - Added basic API for direct operations on rhodecode via JSON
396 - Implemented advanced hook management
397 - Implemented advanced hook management
397
398
398 fixes
399 fixes
399 +++++
400 +++++
400
401
401 - fixed file browser bug, when switching into given form revision the url was
402 - fixed file browser bug, when switching into given form revision the url was
402 not changing
403 not changing
403 - fixed propagation to error controller on simplehg and simplegit middlewares
404 - fixed propagation to error controller on simplehg and simplegit middlewares
404 - fixed error when trying to make a download on empty repository
405 - fixed error when trying to make a download on empty repository
405 - fixed problem with '[' chars in commit messages in journal
406 - fixed problem with '[' chars in commit messages in journal
406 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
407 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
407 - journal fork fixes
408 - journal fork fixes
408 - removed issue with space inside renamed repository after deletion
409 - removed issue with space inside renamed repository after deletion
409 - fixed strange issue on formencode imports
410 - fixed strange issue on formencode imports
410 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
411 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
411 - #150 fixes for errors on repositories mapped in db but corrupted in
412 - #150 fixes for errors on repositories mapped in db but corrupted in
412 filesystem
413 filesystem
413 - fixed problem with ascendant characters in realm #181
414 - fixed problem with ascendant characters in realm #181
414 - fixed problem with sqlite file based database connection pool
415 - fixed problem with sqlite file based database connection pool
415 - whoosh indexer and code stats share the same dynamic extensions map
416 - whoosh indexer and code stats share the same dynamic extensions map
416 - fixes #188 - relationship delete of repo_to_perm entry on user removal
417 - fixes #188 - relationship delete of repo_to_perm entry on user removal
417 - fixes issue #189 Trending source files shows "show more" when no more exist
418 - fixes issue #189 Trending source files shows "show more" when no more exist
418 - fixes issue #197 Relative paths for pidlocks
419 - fixes issue #197 Relative paths for pidlocks
419 - fixes issue #198 password will require only 3 chars now for login form
420 - fixes issue #198 password will require only 3 chars now for login form
420 - fixes issue #199 wrong redirection for non admin users after creating a repository
421 - fixes issue #199 wrong redirection for non admin users after creating a repository
421 - fixes issues #202, bad db constraint made impossible to attach same group
422 - fixes issues #202, bad db constraint made impossible to attach same group
422 more than one time. Affects only mysql/postgres
423 more than one time. Affects only mysql/postgres
423 - fixes #218 os.kill patch for windows was missing sig param
424 - fixes #218 os.kill patch for windows was missing sig param
424 - improved rendering of dag (they are not trimmed anymore when number of
425 - improved rendering of dag (they are not trimmed anymore when number of
425 heads exceeds 5)
426 heads exceeds 5)
426
427
427 1.1.8 (**2011-04-12**)
428 1.1.8 (**2011-04-12**)
428 ----------------------
429 ----------------------
429
430
430 news
431 news
431 ++++
432 ++++
432
433
433 - improved windows support
434 - improved windows support
434
435
435 fixes
436 fixes
436 +++++
437 +++++
437
438
438 - fixed #140 freeze of python dateutil library, since new version is python2.x
439 - fixed #140 freeze of python dateutil library, since new version is python2.x
439 incompatible
440 incompatible
440 - setup-app will check for write permission in given path
441 - setup-app will check for write permission in given path
441 - cleaned up license info issue #149
442 - cleaned up license info issue #149
442 - fixes for issues #137,#116 and problems with unicode and accented characters.
443 - fixes for issues #137,#116 and problems with unicode and accented characters.
443 - fixes crashes on gravatar, when passed in email as unicode
444 - fixes crashes on gravatar, when passed in email as unicode
444 - fixed tooltip flickering problems
445 - fixed tooltip flickering problems
445 - fixed came_from redirection on windows
446 - fixed came_from redirection on windows
446 - fixed logging modules, and sql formatters
447 - fixed logging modules, and sql formatters
447 - windows fixes for os.kill issue #133
448 - windows fixes for os.kill issue #133
448 - fixes path splitting for windows issues #148
449 - fixes path splitting for windows issues #148
449 - fixed issue #143 wrong import on migration to 1.1.X
450 - fixed issue #143 wrong import on migration to 1.1.X
450 - fixed problems with displaying binary files, thanks to Thomas Waldmann
451 - fixed problems with displaying binary files, thanks to Thomas Waldmann
451 - removed name from archive files since it's breaking ui for long repo names
452 - removed name from archive files since it's breaking ui for long repo names
452 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
453 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
453 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
454 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
454 Thomas Waldmann
455 Thomas Waldmann
455 - fixed issue #166 summary pager was skipping 10 revisions on second page
456 - fixed issue #166 summary pager was skipping 10 revisions on second page
456
457
457
458
458 1.1.7 (**2011-03-23**)
459 1.1.7 (**2011-03-23**)
459 ----------------------
460 ----------------------
460
461
461 news
462 news
462 ++++
463 ++++
463
464
464 fixes
465 fixes
465 +++++
466 +++++
466
467
467 - fixed (again) #136 installation support for FreeBSD
468 - fixed (again) #136 installation support for FreeBSD
468
469
469
470
470 1.1.6 (**2011-03-21**)
471 1.1.6 (**2011-03-21**)
471 ----------------------
472 ----------------------
472
473
473 news
474 news
474 ++++
475 ++++
475
476
476 fixes
477 fixes
477 +++++
478 +++++
478
479
479 - fixed #136 installation support for FreeBSD
480 - fixed #136 installation support for FreeBSD
480 - RhodeCode will check for python version during installation
481 - RhodeCode will check for python version during installation
481
482
482 1.1.5 (**2011-03-17**)
483 1.1.5 (**2011-03-17**)
483 ----------------------
484 ----------------------
484
485
485 news
486 news
486 ++++
487 ++++
487
488
488 - basic windows support, by exchanging pybcrypt into sha256 for windows only
489 - basic windows support, by exchanging pybcrypt into sha256 for windows only
489 highly inspired by idea of mantis406
490 highly inspired by idea of mantis406
490
491
491 fixes
492 fixes
492 +++++
493 +++++
493
494
494 - fixed sorting by author in main page
495 - fixed sorting by author in main page
495 - fixed crashes with diffs on binary files
496 - fixed crashes with diffs on binary files
496 - fixed #131 problem with boolean values for LDAP
497 - fixed #131 problem with boolean values for LDAP
497 - fixed #122 mysql problems thanks to striker69
498 - fixed #122 mysql problems thanks to striker69
498 - fixed problem with errors on calling raw/raw_files/annotate functions
499 - fixed problem with errors on calling raw/raw_files/annotate functions
499 with unknown revisions
500 with unknown revisions
500 - fixed returned rawfiles attachment names with international character
501 - fixed returned rawfiles attachment names with international character
501 - cleaned out docs, big thanks to Jason Harris
502 - cleaned out docs, big thanks to Jason Harris
502
503
503 1.1.4 (**2011-02-19**)
504 1.1.4 (**2011-02-19**)
504 ----------------------
505 ----------------------
505
506
506 news
507 news
507 ++++
508 ++++
508
509
509 fixes
510 fixes
510 +++++
511 +++++
511
512
512 - fixed formencode import problem on settings page, that caused server crash
513 - fixed formencode import problem on settings page, that caused server crash
513 when that page was accessed as first after server start
514 when that page was accessed as first after server start
514 - journal fixes
515 - journal fixes
515 - fixed option to access repository just by entering http://server/<repo_name>
516 - fixed option to access repository just by entering http://server/<repo_name>
516
517
517 1.1.3 (**2011-02-16**)
518 1.1.3 (**2011-02-16**)
518 ----------------------
519 ----------------------
519
520
520 news
521 news
521 ++++
522 ++++
522
523
523 - implemented #102 allowing the '.' character in username
524 - implemented #102 allowing the '.' character in username
524 - added option to access repository just by entering http://server/<repo_name>
525 - added option to access repository just by entering http://server/<repo_name>
525 - celery task ignores result for better performance
526 - celery task ignores result for better performance
526
527
527 fixes
528 fixes
528 +++++
529 +++++
529
530
530 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
531 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
531 apollo13 and Johan Walles
532 apollo13 and Johan Walles
532 - small fixes in journal
533 - small fixes in journal
533 - fixed problems with getting setting for celery from .ini files
534 - fixed problems with getting setting for celery from .ini files
534 - registration, password reset and login boxes share the same title as main
535 - registration, password reset and login boxes share the same title as main
535 application now
536 application now
536 - fixed #113: to high permissions to fork repository
537 - fixed #113: to high permissions to fork repository
537 - fixed problem with '[' chars in commit messages in journal
538 - fixed problem with '[' chars in commit messages in journal
538 - removed issue with space inside renamed repository after deletion
539 - removed issue with space inside renamed repository after deletion
539 - db transaction fixes when filesystem repository creation failed
540 - db transaction fixes when filesystem repository creation failed
540 - fixed #106 relation issues on databases different than sqlite
541 - fixed #106 relation issues on databases different than sqlite
541 - fixed static files paths links to use of url() method
542 - fixed static files paths links to use of url() method
542
543
543 1.1.2 (**2011-01-12**)
544 1.1.2 (**2011-01-12**)
544 ----------------------
545 ----------------------
545
546
546 news
547 news
547 ++++
548 ++++
548
549
549
550
550 fixes
551 fixes
551 +++++
552 +++++
552
553
553 - fixes #98 protection against float division of percentage stats
554 - fixes #98 protection against float division of percentage stats
554 - fixed graph bug
555 - fixed graph bug
555 - forced webhelpers version since it was making troubles during installation
556 - forced webhelpers version since it was making troubles during installation
556
557
557 1.1.1 (**2011-01-06**)
558 1.1.1 (**2011-01-06**)
558 ----------------------
559 ----------------------
559
560
560 news
561 news
561 ++++
562 ++++
562
563
563 - added force https option into ini files for easier https usage (no need to
564 - added force https option into ini files for easier https usage (no need to
564 set server headers with this options)
565 set server headers with this options)
565 - small css updates
566 - small css updates
566
567
567 fixes
568 fixes
568 +++++
569 +++++
569
570
570 - fixed #96 redirect loop on files view on repositories without changesets
571 - fixed #96 redirect loop on files view on repositories without changesets
571 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
572 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
572 and server crashed with errors
573 and server crashed with errors
573 - fixed large tooltips problems on main page
574 - fixed large tooltips problems on main page
574 - fixed #92 whoosh indexer is more error proof
575 - fixed #92 whoosh indexer is more error proof
575
576
576 1.1.0 (**2010-12-18**)
577 1.1.0 (**2010-12-18**)
577 ----------------------
578 ----------------------
578
579
579 news
580 news
580 ++++
581 ++++
581
582
582 - rewrite of internals for vcs >=0.1.10
583 - rewrite of internals for vcs >=0.1.10
583 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
584 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
584 with older clients
585 with older clients
585 - anonymous access, authentication via ldap
586 - anonymous access, authentication via ldap
586 - performance upgrade for cached repos list - each repository has its own
587 - performance upgrade for cached repos list - each repository has its own
587 cache that's invalidated when needed.
588 cache that's invalidated when needed.
588 - performance upgrades on repositories with large amount of commits (20K+)
589 - performance upgrades on repositories with large amount of commits (20K+)
589 - main page quick filter for filtering repositories
590 - main page quick filter for filtering repositories
590 - user dashboards with ability to follow chosen repositories actions
591 - user dashboards with ability to follow chosen repositories actions
591 - sends email to admin on new user registration
592 - sends email to admin on new user registration
592 - added cache/statistics reset options into repository settings
593 - added cache/statistics reset options into repository settings
593 - more detailed action logger (based on hooks) with pushed changesets lists
594 - more detailed action logger (based on hooks) with pushed changesets lists
594 and options to disable those hooks from admin panel
595 and options to disable those hooks from admin panel
595 - introduced new enhanced changelog for merges that shows more accurate results
596 - introduced new enhanced changelog for merges that shows more accurate results
596 - new improved and faster code stats (based on pygments lexers mapping tables,
597 - new improved and faster code stats (based on pygments lexers mapping tables,
597 showing up to 10 trending sources for each repository. Additionally stats
598 showing up to 10 trending sources for each repository. Additionally stats
598 can be disabled in repository settings.
599 can be disabled in repository settings.
599 - gui optimizations, fixed application width to 1024px
600 - gui optimizations, fixed application width to 1024px
600 - added cut off (for large files/changesets) limit into config files
601 - added cut off (for large files/changesets) limit into config files
601 - whoosh, celeryd, upgrade moved to paster command
602 - whoosh, celeryd, upgrade moved to paster command
602 - other than sqlite database backends can be used
603 - other than sqlite database backends can be used
603
604
604 fixes
605 fixes
605 +++++
606 +++++
606
607
607 - fixes #61 forked repo was showing only after cache expired
608 - fixes #61 forked repo was showing only after cache expired
608 - fixes #76 no confirmation on user deletes
609 - fixes #76 no confirmation on user deletes
609 - fixes #66 Name field misspelled
610 - fixes #66 Name field misspelled
610 - fixes #72 block user removal when he owns repositories
611 - fixes #72 block user removal when he owns repositories
611 - fixes #69 added password confirmation fields
612 - fixes #69 added password confirmation fields
612 - fixes #87 RhodeCode crashes occasionally on updating repository owner
613 - fixes #87 RhodeCode crashes occasionally on updating repository owner
613 - fixes #82 broken annotations on files with more than 1 blank line at the end
614 - fixes #82 broken annotations on files with more than 1 blank line at the end
614 - a lot of fixes and tweaks for file browser
615 - a lot of fixes and tweaks for file browser
615 - fixed detached session issues
616 - fixed detached session issues
616 - fixed when user had no repos he would see all repos listed in my account
617 - fixed when user had no repos he would see all repos listed in my account
617 - fixed ui() instance bug when global hgrc settings was loaded for server
618 - fixed ui() instance bug when global hgrc settings was loaded for server
618 instance and all hgrc options were merged with our db ui() object
619 instance and all hgrc options were merged with our db ui() object
619 - numerous small bugfixes
620 - numerous small bugfixes
620
621
621 (special thanks for TkSoh for detailed feedback)
622 (special thanks for TkSoh for detailed feedback)
622
623
623
624
624 1.0.2 (**2010-11-12**)
625 1.0.2 (**2010-11-12**)
625 ----------------------
626 ----------------------
626
627
627 news
628 news
628 ++++
629 ++++
629
630
630 - tested under python2.7
631 - tested under python2.7
631 - bumped sqlalchemy and celery versions
632 - bumped sqlalchemy and celery versions
632
633
633 fixes
634 fixes
634 +++++
635 +++++
635
636
636 - fixed #59 missing graph.js
637 - fixed #59 missing graph.js
637 - fixed repo_size crash when repository had broken symlinks
638 - fixed repo_size crash when repository had broken symlinks
638 - fixed python2.5 crashes.
639 - fixed python2.5 crashes.
639
640
640
641
641 1.0.1 (**2010-11-10**)
642 1.0.1 (**2010-11-10**)
642 ----------------------
643 ----------------------
643
644
644 news
645 news
645 ++++
646 ++++
646
647
647 - small css updated
648 - small css updated
648
649
649 fixes
650 fixes
650 +++++
651 +++++
651
652
652 - fixed #53 python2.5 incompatible enumerate calls
653 - fixed #53 python2.5 incompatible enumerate calls
653 - fixed #52 disable mercurial extension for web
654 - fixed #52 disable mercurial extension for web
654 - fixed #51 deleting repositories don't delete it's dependent objects
655 - fixed #51 deleting repositories don't delete it's dependent objects
655
656
656
657
657 1.0.0 (**2010-11-02**)
658 1.0.0 (**2010-11-02**)
658 ----------------------
659 ----------------------
659
660
660 - security bugfix simplehg wasn't checking for permissions on commands
661 - security bugfix simplehg wasn't checking for permissions on commands
661 other than pull or push.
662 other than pull or push.
662 - fixed doubled messages after push or pull in admin journal
663 - fixed doubled messages after push or pull in admin journal
663 - templating and css corrections, fixed repo switcher on chrome, updated titles
664 - templating and css corrections, fixed repo switcher on chrome, updated titles
664 - admin menu accessible from options menu on repository view
665 - admin menu accessible from options menu on repository view
665 - permissions cached queries
666 - permissions cached queries
666
667
667 1.0.0rc4 (**2010-10-12**)
668 1.0.0rc4 (**2010-10-12**)
668 --------------------------
669 --------------------------
669
670
670 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
671 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
671 - removed cache_manager settings from sqlalchemy meta
672 - removed cache_manager settings from sqlalchemy meta
672 - added sqlalchemy cache settings to ini files
673 - added sqlalchemy cache settings to ini files
673 - validated password length and added second try of failure on paster setup-app
674 - validated password length and added second try of failure on paster setup-app
674 - fixed setup database destroy prompt even when there was no db
675 - fixed setup database destroy prompt even when there was no db
675
676
676
677
677 1.0.0rc3 (**2010-10-11**)
678 1.0.0rc3 (**2010-10-11**)
678 -------------------------
679 -------------------------
679
680
680 - fixed i18n during installation.
681 - fixed i18n during installation.
681
682
682 1.0.0rc2 (**2010-10-11**)
683 1.0.0rc2 (**2010-10-11**)
683 -------------------------
684 -------------------------
684
685
685 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
686 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
686 occure. After vcs is fixed it'll be put back again.
687 occure. After vcs is fixed it'll be put back again.
687 - templating/css rewrites, optimized css. No newline at end of file
688 - templating/css rewrites, optimized css.
@@ -1,102 +1,102 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.backup_manager
3 rhodecode.bin.backup_manager
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Mercurial repositories backup manager, it allows to backups all
6 Repositories backup manager, it allows to backups all
7 repositories and send it to backup server using RSA key via ssh.
7 repositories and send it to backup server using RSA key via ssh.
8
8
9 :created_on: Feb 28, 2010
9 :created_on: Feb 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 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 sys
28 import sys
29
29
30 import logging
30 import logging
31 import tarfile
31 import tarfile
32 import datetime
32 import datetime
33 import subprocess
33 import subprocess
34
34
35 logging.basicConfig(level=logging.DEBUG,
35 logging.basicConfig(level=logging.DEBUG,
36 format="%(asctime)s %(levelname)-5.5s %(message)s")
36 format="%(asctime)s %(levelname)-5.5s %(message)s")
37
37
38
38
39 class BackupManager(object):
39 class BackupManager(object):
40 def __init__(self, repos_location, rsa_key, backup_server):
40 def __init__(self, repos_location, rsa_key, backup_server):
41 today = datetime.datetime.now().weekday() + 1
41 today = datetime.datetime.now().weekday() + 1
42 self.backup_file_name = "mercurial_repos.%s.tar.gz" % today
42 self.backup_file_name = "rhodecode_repos.%s.tar.gz" % today
43
43
44 self.id_rsa_path = self.get_id_rsa(rsa_key)
44 self.id_rsa_path = self.get_id_rsa(rsa_key)
45 self.repos_path = self.get_repos_path(repos_location)
45 self.repos_path = self.get_repos_path(repos_location)
46 self.backup_server = backup_server
46 self.backup_server = backup_server
47
47
48 self.backup_file_path = '/tmp'
48 self.backup_file_path = '/tmp'
49
49
50 logging.info('starting backup for %s', self.repos_path)
50 logging.info('starting backup for %s', self.repos_path)
51 logging.info('backup target %s', self.backup_file_path)
51 logging.info('backup target %s', self.backup_file_path)
52
52
53 def get_id_rsa(self, rsa_key):
53 def get_id_rsa(self, rsa_key):
54 if not os.path.isfile(rsa_key):
54 if not os.path.isfile(rsa_key):
55 logging.error('Could not load id_rsa key file in %s', rsa_key)
55 logging.error('Could not load id_rsa key file in %s', rsa_key)
56 sys.exit()
56 sys.exit()
57 return rsa_key
57 return rsa_key
58
58
59 def get_repos_path(self, path):
59 def get_repos_path(self, path):
60 if not os.path.isdir(path):
60 if not os.path.isdir(path):
61 logging.error('Wrong location for repositories in %s', path)
61 logging.error('Wrong location for repositories in %s', path)
62 sys.exit()
62 sys.exit()
63 return path
63 return path
64
64
65 def backup_repos(self):
65 def backup_repos(self):
66 bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
66 bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
67 tar = tarfile.open(bckp_file, "w:gz")
67 tar = tarfile.open(bckp_file, "w:gz")
68
68
69 for dir_name in os.listdir(self.repos_path):
69 for dir_name in os.listdir(self.repos_path):
70 logging.info('backing up %s', dir_name)
70 logging.info('backing up %s', dir_name)
71 tar.add(os.path.join(self.repos_path, dir_name), dir_name)
71 tar.add(os.path.join(self.repos_path, dir_name), dir_name)
72 tar.close()
72 tar.close()
73 logging.info('finished backup of mercurial repositories')
73 logging.info('finished backup of mercurial repositories')
74
74
75 def transfer_files(self):
75 def transfer_files(self):
76 params = {
76 params = {
77 'id_rsa_key': self.id_rsa_path,
77 'id_rsa_key': self.id_rsa_path,
78 'backup_file': os.path.join(self.backup_file_path,
78 'backup_file': os.path.join(self.backup_file_path,
79 self.backup_file_name),
79 self.backup_file_name),
80 'backup_server': self.backup_server
80 'backup_server': self.backup_server
81 }
81 }
82 cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
82 cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
83 '%(backup_file)s' % params,
83 '%(backup_file)s' % params,
84 '%(backup_server)s' % params]
84 '%(backup_server)s' % params]
85
85
86 subprocess.call(cmd)
86 subprocess.call(cmd)
87 logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
87 logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
88
88
89 def rm_file(self):
89 def rm_file(self):
90 logging.info('Removing file %s', self.backup_file_name)
90 logging.info('Removing file %s', self.backup_file_name)
91 os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
91 os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
92
92
93 if __name__ == "__main__":
93 if __name__ == "__main__":
94
94
95 repo_location = '/home/repo_path'
95 repo_location = '/home/repo_path'
96 backup_server = 'root@192.168.1.100:/backups/mercurial'
96 backup_server = 'root@192.168.1.100:/backups/mercurial'
97 rsa_key = '/home/id_rsa'
97 rsa_key = '/home/id_rsa'
98
98
99 B_MANAGER = BackupManager(repo_location, rsa_key, backup_server)
99 B_MANAGER = BackupManager(repo_location, rsa_key, backup_server)
100 B_MANAGER.backup_repos()
100 B_MANAGER.backup_repos()
101 B_MANAGER.transfer_files()
101 B_MANAGER.transfer_files()
102 B_MANAGER.rm_file()
102 B_MANAGER.rm_file()
@@ -1,262 +1,275 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, retid=None, 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 return Response(
66 status=code,
66 body=json.dumps(dict(id=retid, result=None, error=message)),
67 content_type='application/json')
67 status=code,
68 return resp
68 content_type='application/json'
69 )
69
70
70
71
71 class JSONRPCController(WSGIController):
72 class JSONRPCController(WSGIController):
72 """
73 """
73 A WSGI-speaking JSON-RPC controller class
74 A WSGI-speaking JSON-RPC controller class
74
75
75 See the specification:
76 See the specification:
76 <http://json-rpc.org/wiki/specification>`.
77 <http://json-rpc.org/wiki/specification>`.
77
78
78 Valid controller return values should be json-serializable objects.
79 Valid controller return values should be json-serializable objects.
79
80
80 Sub-classes should catch their exceptions and raise JSONRPCError
81 Sub-classes should catch their exceptions and raise JSONRPCError
81 if they want to pass meaningful errors to the client.
82 if they want to pass meaningful errors to the client.
82
83
83 """
84 """
84
85
85 def _get_method_args(self):
86 def _get_method_args(self):
86 """
87 """
87 Return `self._rpc_args` to dispatched controller method
88 Return `self._rpc_args` to dispatched controller method
88 chosen by __call__
89 chosen by __call__
89 """
90 """
90 return self._rpc_args
91 return self._rpc_args
91
92
92 def __call__(self, environ, start_response):
93 def __call__(self, environ, start_response):
93 """
94 """
94 Parse the request body as JSON, look up the method on the
95 Parse the request body as JSON, look up the method on the
95 controller and if it exists, dispatch to it.
96 controller and if it exists, dispatch to it.
96 """
97 """
98 self._req_id = None
97 if 'CONTENT_LENGTH' not in environ:
99 if 'CONTENT_LENGTH' not in environ:
98 log.debug("No Content-Length")
100 log.debug("No Content-Length")
99 return jsonrpc_error(message="No Content-Length in request")
101 return jsonrpc_error(retid=self._req_id,
102 message="No Content-Length in request")
100 else:
103 else:
101 length = environ['CONTENT_LENGTH'] or 0
104 length = environ['CONTENT_LENGTH'] or 0
102 length = int(environ['CONTENT_LENGTH'])
105 length = int(environ['CONTENT_LENGTH'])
103 log.debug('Content-Length: %s' % length)
106 log.debug('Content-Length: %s' % length)
104
107
105 if length == 0:
108 if length == 0:
106 log.debug("Content-Length is 0")
109 log.debug("Content-Length is 0")
107 return jsonrpc_error(message="Content-Length is 0")
110 return jsonrpc_error(retid=self._req_id,
111 message="Content-Length is 0")
108
112
109 raw_body = environ['wsgi.input'].read(length)
113 raw_body = environ['wsgi.input'].read(length)
110
114
111 try:
115 try:
112 json_body = json.loads(urllib.unquote_plus(raw_body))
116 json_body = json.loads(urllib.unquote_plus(raw_body))
113 except ValueError, e:
117 except ValueError, e:
114 # catch JSON errors Here
118 # catch JSON errors Here
115 return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
119 return jsonrpc_error(retid=self._req_id,
120 message="JSON parse error ERR:%s RAW:%r" \
116 % (e, urllib.unquote_plus(raw_body)))
121 % (e, urllib.unquote_plus(raw_body)))
117
122
118 # check AUTH based on API KEY
123 # check AUTH based on API KEY
119 try:
124 try:
120 self._req_api_key = json_body['api_key']
125 self._req_api_key = json_body['api_key']
121 self._req_id = json_body['id']
126 self._req_id = json_body['id']
122 self._req_method = json_body['method']
127 self._req_method = json_body['method']
123 self._request_params = json_body['args']
128 self._request_params = json_body['args']
124 log.debug(
129 log.debug(
125 'method: %s, params: %s' % (self._req_method,
130 'method: %s, params: %s' % (self._req_method,
126 self._request_params)
131 self._request_params)
127 )
132 )
128 except KeyError, e:
133 except KeyError, e:
129 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
134 return jsonrpc_error(retid=self._req_id,
135 message='Incorrect JSON query missing %s' % e)
130
136
131 # check if we can find this session using api_key
137 # check if we can find this session using api_key
132 try:
138 try:
133 u = User.get_by_api_key(self._req_api_key)
139 u = User.get_by_api_key(self._req_api_key)
134 if u is None:
140 if u is None:
135 return jsonrpc_error(message='Invalid API KEY')
141 return jsonrpc_error(retid=self._req_id,
142 message='Invalid API KEY')
136 auth_u = AuthUser(u.user_id, self._req_api_key)
143 auth_u = AuthUser(u.user_id, self._req_api_key)
137 except Exception, e:
144 except Exception, e:
138 return jsonrpc_error(message='Invalid API KEY')
145 return jsonrpc_error(retid=self._req_id,
146 message='Invalid API KEY')
139
147
140 self._error = None
148 self._error = None
141 try:
149 try:
142 self._func = self._find_method()
150 self._func = self._find_method()
143 except AttributeError, e:
151 except AttributeError, e:
144 return jsonrpc_error(message=str(e))
152 return jsonrpc_error(retid=self._req_id,
153 message=str(e))
145
154
146 # now that we have a method, add self._req_params to
155 # now that we have a method, add self._req_params to
147 # self.kargs and dispatch control to WGIController
156 # self.kargs and dispatch control to WGIController
148 argspec = inspect.getargspec(self._func)
157 argspec = inspect.getargspec(self._func)
149 arglist = argspec[0][1:]
158 arglist = argspec[0][1:]
150 defaults = map(type, argspec[3] or [])
159 defaults = map(type, argspec[3] or [])
151 default_empty = types.NotImplementedType
160 default_empty = types.NotImplementedType
152
161
153 # kw arguments required by this method
162 # kw arguments required by this method
154 func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
163 func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
155 fillvalue=default_empty))
164 fillvalue=default_empty))
156
165
157 # this is little trick to inject logged in user for
166 # this is little trick to inject logged in user for
158 # perms decorators to work they expect the controller class to have
167 # perms decorators to work they expect the controller class to have
159 # rhodecode_user attribute set
168 # rhodecode_user attribute set
160 self.rhodecode_user = auth_u
169 self.rhodecode_user = auth_u
161
170
162 # This attribute will need to be first param of a method that uses
171 # 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
172 # api_key, which is translated to instance of user at that name
164 USER_SESSION_ATTR = 'apiuser'
173 USER_SESSION_ATTR = 'apiuser'
165
174
166 if USER_SESSION_ATTR not in arglist:
175 if USER_SESSION_ATTR not in arglist:
167 return jsonrpc_error(message='This method [%s] does not support '
176 return jsonrpc_error(
168 'authentication (missing %s param)' %
177 retid=self._req_id,
169 (self._func.__name__, USER_SESSION_ATTR))
178 message='This method [%s] does not support '
179 'authentication (missing %s param)' % (
180 self._func.__name__, USER_SESSION_ATTR)
181 )
170
182
171 # get our arglist and check if we provided them as args
183 # get our arglist and check if we provided them as args
172 for arg, default in func_kwargs.iteritems():
184 for arg, default in func_kwargs.iteritems():
173 if arg == USER_SESSION_ATTR:
185 if arg == USER_SESSION_ATTR:
174 # USER_SESSION_ATTR is something translated from api key and
186 # USER_SESSION_ATTR is something translated from api key and
175 # this is checked before so we don't need validate it
187 # this is checked before so we don't need validate it
176 continue
188 continue
177
189
178 # skip the required param check if it's default value is
190 # skip the required param check if it's default value is
179 # NotImplementedType (default_empty)
191 # NotImplementedType (default_empty)
180 if (default == default_empty and arg not in self._request_params):
192 if (default == default_empty and arg not in self._request_params):
181 return jsonrpc_error(
193 return jsonrpc_error(
194 retid=self._req_id,
182 message=(
195 message=(
183 'Missing non optional `%s` arg in JSON DATA' % arg
196 'Missing non optional `%s` arg in JSON DATA' % arg
184 )
197 )
185 )
198 )
186
199
187 self._rpc_args = {USER_SESSION_ATTR: u}
200 self._rpc_args = {USER_SESSION_ATTR: u}
188 self._rpc_args.update(self._request_params)
201 self._rpc_args.update(self._request_params)
189
202
190 self._rpc_args['action'] = self._req_method
203 self._rpc_args['action'] = self._req_method
191 self._rpc_args['environ'] = environ
204 self._rpc_args['environ'] = environ
192 self._rpc_args['start_response'] = start_response
205 self._rpc_args['start_response'] = start_response
193
206
194 status = []
207 status = []
195 headers = []
208 headers = []
196 exc_info = []
209 exc_info = []
197
210
198 def change_content(new_status, new_headers, new_exc_info=None):
211 def change_content(new_status, new_headers, new_exc_info=None):
199 status.append(new_status)
212 status.append(new_status)
200 headers.extend(new_headers)
213 headers.extend(new_headers)
201 exc_info.append(new_exc_info)
214 exc_info.append(new_exc_info)
202
215
203 output = WSGIController.__call__(self, environ, change_content)
216 output = WSGIController.__call__(self, environ, change_content)
204 output = list(output)
217 output = list(output)
205 headers.append(('Content-Length', str(len(output[0]))))
218 headers.append(('Content-Length', str(len(output[0]))))
206 replace_header(headers, 'Content-Type', 'application/json')
219 replace_header(headers, 'Content-Type', 'application/json')
207 start_response(status[0], headers, exc_info[0])
220 start_response(status[0], headers, exc_info[0])
208
221
209 return output
222 return output
210
223
211 def _dispatch_call(self):
224 def _dispatch_call(self):
212 """
225 """
213 Implement dispatch interface specified by WSGIController
226 Implement dispatch interface specified by WSGIController
214 """
227 """
215 try:
228 try:
216 raw_response = self._inspect_call(self._func)
229 raw_response = self._inspect_call(self._func)
217 if isinstance(raw_response, HTTPError):
230 if isinstance(raw_response, HTTPError):
218 self._error = str(raw_response)
231 self._error = str(raw_response)
219 except JSONRPCError, e:
232 except JSONRPCError, e:
220 self._error = str(e)
233 self._error = str(e)
221 except Exception, e:
234 except Exception, e:
222 log.error('Encountered unhandled exception: %s' \
235 log.error('Encountered unhandled exception: %s' \
223 % traceback.format_exc())
236 % traceback.format_exc())
224 json_exc = JSONRPCError('Internal server error')
237 json_exc = JSONRPCError('Internal server error')
225 self._error = str(json_exc)
238 self._error = str(json_exc)
226
239
227 if self._error is not None:
240 if self._error is not None:
228 raw_response = None
241 raw_response = None
229
242
230 response = dict(id=self._req_id, result=raw_response,
243 response = dict(id=self._req_id, result=raw_response,
231 error=self._error)
244 error=self._error)
232
245
233 try:
246 try:
234 return json.dumps(response)
247 return json.dumps(response)
235 except TypeError, e:
248 except TypeError, e:
236 log.error('API FAILED. Error encoding response: %s' % e)
249 log.error('API FAILED. Error encoding response: %s' % e)
237 return json.dumps(
250 return json.dumps(
238 dict(
251 dict(
239 id=self._req_id,
252 id=self._req_id,
240 result=None,
253 result=None,
241 error="Error encoding response"
254 error="Error encoding response"
242 )
255 )
243 )
256 )
244
257
245 def _find_method(self):
258 def _find_method(self):
246 """
259 """
247 Return method named by `self._req_method` in controller if able
260 Return method named by `self._req_method` in controller if able
248 """
261 """
249 log.debug('Trying to find JSON-RPC method: %s' % self._req_method)
262 log.debug('Trying to find JSON-RPC method: %s' % self._req_method)
250 if self._req_method.startswith('_'):
263 if self._req_method.startswith('_'):
251 raise AttributeError("Method not allowed")
264 raise AttributeError("Method not allowed")
252
265
253 try:
266 try:
254 func = getattr(self, self._req_method, None)
267 func = getattr(self, self._req_method, None)
255 except UnicodeEncodeError:
268 except UnicodeEncodeError:
256 raise AttributeError("Problem decoding unicode in requested "
269 raise AttributeError("Problem decoding unicode in requested "
257 "method name.")
270 "method name.")
258
271
259 if isinstance(func, types.MethodType):
272 if isinstance(func, types.MethodType):
260 return func
273 return func
261 else:
274 else:
262 raise AttributeError("No such method: %s" % self._req_method)
275 raise AttributeError("No such method: %s" % self._req_method)
@@ -1,706 +1,706 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, AuthUser
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, 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.lib.utils import map_groups
41 from rhodecode.lib.utils import map_groups
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class ApiController(JSONRPCController):
46 class ApiController(JSONRPCController):
47 """
47 """
48 API Controller
48 API Controller
49
49
50
50
51 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
52 API_KEY propagated as instance of user object
52 API_KEY propagated as instance of user object
53
53
54 Preferably this should be first argument also
54 Preferably this should be first argument also
55
55
56
56
57 Each function should also **raise** JSONRPCError for any
57 Each function should also **raise** JSONRPCError for any
58 errors that happens
58 errors that happens
59
59
60 """
60 """
61
61
62 @HasPermissionAllDecorator('hg.admin')
62 @HasPermissionAllDecorator('hg.admin')
63 def pull(self, apiuser, repo_name):
63 def pull(self, apiuser, repo_name):
64 """
64 """
65 Dispatch pull action on given repo
65 Dispatch pull action on given repo
66
66
67
67
68 :param user:
68 :param user:
69 :param repo_name:
69 :param repo_name:
70 """
70 """
71
71
72 if Repository.is_valid(repo_name) is False:
72 if Repository.is_valid(repo_name) is False:
73 raise JSONRPCError('Unknown repo "%s"' % repo_name)
73 raise JSONRPCError('Unknown repo "%s"' % repo_name)
74
74
75 try:
75 try:
76 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
76 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
77 return 'Pulled from %s' % repo_name
77 return 'Pulled from %s' % repo_name
78 except Exception:
78 except Exception:
79 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
79 raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
80
80
81 @HasPermissionAllDecorator('hg.admin')
81 @HasPermissionAllDecorator('hg.admin')
82 def get_user(self, apiuser, userid):
82 def get_user(self, apiuser, userid):
83 """"
83 """"
84 Get a user by username
84 Get a user by username
85
85
86 :param apiuser:
86 :param apiuser:
87 :param username:
87 :param username:
88 """
88 """
89
89
90 user = UserModel().get_user(userid)
90 user = UserModel().get_user(userid)
91 if user is None:
91 if user is None:
92 return user
92 return user
93
93
94 return dict(
94 return dict(
95 id=user.user_id,
95 id=user.user_id,
96 username=user.username,
96 username=user.username,
97 firstname=user.name,
97 firstname=user.name,
98 lastname=user.lastname,
98 lastname=user.lastname,
99 email=user.email,
99 email=user.email,
100 active=user.active,
100 active=user.active,
101 admin=user.admin,
101 admin=user.admin,
102 ldap_dn=user.ldap_dn,
102 ldap_dn=user.ldap_dn,
103 last_login=user.last_login,
103 last_login=user.last_login,
104 permissions=AuthUser(user_id=user.user_id).permissions
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 last_login=user.last_login,
128 )
128 )
129 )
129 )
130 return result
130 return result
131
131
132 @HasPermissionAllDecorator('hg.admin')
132 @HasPermissionAllDecorator('hg.admin')
133 def create_user(self, apiuser, username, email, password, firstname=None,
133 def create_user(self, apiuser, username, email, password, firstname=None,
134 lastname=None, active=True, admin=False, ldap_dn=None):
134 lastname=None, active=True, admin=False, ldap_dn=None):
135 """
135 """
136 Create new user
136 Create new user
137
137
138 :param apiuser:
138 :param apiuser:
139 :param username:
139 :param username:
140 :param password:
140 :param password:
141 :param email:
141 :param email:
142 :param name:
142 :param name:
143 :param lastname:
143 :param lastname:
144 :param active:
144 :param active:
145 :param admin:
145 :param admin:
146 :param ldap_dn:
146 :param ldap_dn:
147 """
147 """
148 if User.get_by_username(username):
148 if User.get_by_username(username):
149 raise JSONRPCError("user %s already exist" % username)
149 raise JSONRPCError("user %s already exist" % username)
150
150
151 if User.get_by_email(email, case_insensitive=True):
151 if User.get_by_email(email, case_insensitive=True):
152 raise JSONRPCError("email %s already exist" % email)
152 raise JSONRPCError("email %s already exist" % email)
153
153
154 if ldap_dn:
154 if ldap_dn:
155 # generate temporary password if ldap_dn
155 # generate temporary password if ldap_dn
156 password = PasswordGenerator().gen_password(length=8)
156 password = PasswordGenerator().gen_password(length=8)
157
157
158 try:
158 try:
159 user = UserModel().create_or_update(
159 user = UserModel().create_or_update(
160 username, password, email, firstname,
160 username, password, email, firstname,
161 lastname, active, admin, ldap_dn
161 lastname, active, admin, ldap_dn
162 )
162 )
163 Session.commit()
163 Session.commit()
164 return dict(
164 return dict(
165 id=user.user_id,
165 id=user.user_id,
166 msg='created new user %s' % username,
166 msg='created new user %s' % username,
167 user=dict(
167 user=dict(
168 id=user.user_id,
168 id=user.user_id,
169 username=user.username,
169 username=user.username,
170 firstname=user.name,
170 firstname=user.name,
171 lastname=user.lastname,
171 lastname=user.lastname,
172 email=user.email,
172 email=user.email,
173 active=user.active,
173 active=user.active,
174 admin=user.admin,
174 admin=user.admin,
175 ldap_dn=user.ldap_dn,
175 ldap_dn=user.ldap_dn,
176 last_login=user.last_login,
176 last_login=user.last_login,
177 )
177 )
178 )
178 )
179 except Exception:
179 except Exception:
180 log.error(traceback.format_exc())
180 log.error(traceback.format_exc())
181 raise JSONRPCError('failed to create user %s' % username)
181 raise JSONRPCError('failed to create user %s' % username)
182
182
183 @HasPermissionAllDecorator('hg.admin')
183 @HasPermissionAllDecorator('hg.admin')
184 def update_user(self, apiuser, userid, username, password, email,
184 def update_user(self, apiuser, userid, username, password, email,
185 firstname, lastname, active, admin, ldap_dn):
185 firstname, lastname, active, admin, ldap_dn):
186 """
186 """
187 Updates given user
187 Updates given user
188
188
189 :param apiuser:
189 :param apiuser:
190 :param username:
190 :param username:
191 :param password:
191 :param password:
192 :param email:
192 :param email:
193 :param name:
193 :param name:
194 :param lastname:
194 :param lastname:
195 :param active:
195 :param active:
196 :param admin:
196 :param admin:
197 :param ldap_dn:
197 :param ldap_dn:
198 """
198 """
199 usr = UserModel().get_user(userid)
199 usr = UserModel().get_user(userid)
200 if not usr:
200 if not usr:
201 raise JSONRPCError("user ID:%s does not exist" % userid)
201 raise JSONRPCError("user ID:%s does not exist" % userid)
202
202
203 try:
203 try:
204 usr = UserModel().create_or_update(
204 usr = UserModel().create_or_update(
205 username, password, email, firstname,
205 username, password, email, firstname,
206 lastname, active, admin, ldap_dn
206 lastname, active, admin, ldap_dn
207 )
207 )
208 Session.commit()
208 Session.commit()
209 return dict(
209 return dict(
210 id=usr.user_id,
210 id=usr.user_id,
211 msg='updated user ID:%s %s' % (usr.user_id, usr.username)
211 msg='updated user ID:%s %s' % (usr.user_id, usr.username)
212 )
212 )
213 except Exception:
213 except Exception:
214 log.error(traceback.format_exc())
214 log.error(traceback.format_exc())
215 raise JSONRPCError('failed to update user %s' % userid)
215 raise JSONRPCError('failed to update user %s' % userid)
216
216
217 @HasPermissionAllDecorator('hg.admin')
217 @HasPermissionAllDecorator('hg.admin')
218 def delete_user(self, apiuser, userid):
218 def delete_user(self, apiuser, userid):
219 """"
219 """"
220 Deletes an user
220 Deletes an user
221
221
222 :param apiuser:
222 :param apiuser:
223 """
223 """
224 usr = UserModel().get_user(userid)
224 usr = UserModel().get_user(userid)
225 if not usr:
225 if not usr:
226 raise JSONRPCError("user ID:%s does not exist" % userid)
226 raise JSONRPCError("user ID:%s does not exist" % userid)
227
227
228 try:
228 try:
229 UserModel().delete(userid)
229 UserModel().delete(userid)
230 Session.commit()
230 Session.commit()
231 return dict(
231 return dict(
232 id=usr.user_id,
232 id=usr.user_id,
233 msg='deleted user ID:%s %s' % (usr.user_id, usr.username)
233 msg='deleted user ID:%s %s' % (usr.user_id, usr.username)
234 )
234 )
235 except Exception:
235 except Exception:
236 log.error(traceback.format_exc())
236 log.error(traceback.format_exc())
237 raise JSONRPCError('failed to delete ID:%s %s' % (usr.user_id,
237 raise JSONRPCError('failed to delete ID:%s %s' % (usr.user_id,
238 usr.username))
238 usr.username))
239
239
240 @HasPermissionAllDecorator('hg.admin')
240 @HasPermissionAllDecorator('hg.admin')
241 def get_users_group(self, apiuser, group_name):
241 def get_users_group(self, apiuser, group_name):
242 """"
242 """"
243 Get users group by name
243 Get users group by name
244
244
245 :param apiuser:
245 :param apiuser:
246 :param group_name:
246 :param group_name:
247 """
247 """
248
248
249 users_group = UsersGroup.get_by_group_name(group_name)
249 users_group = UsersGroup.get_by_group_name(group_name)
250 if not users_group:
250 if not users_group:
251 return None
251 return None
252
252
253 members = []
253 members = []
254 for user in users_group.members:
254 for user in users_group.members:
255 user = user.user
255 user = user.user
256 members.append(dict(id=user.user_id,
256 members.append(dict(id=user.user_id,
257 username=user.username,
257 username=user.username,
258 firstname=user.name,
258 firstname=user.name,
259 lastname=user.lastname,
259 lastname=user.lastname,
260 email=user.email,
260 email=user.email,
261 active=user.active,
261 active=user.active,
262 admin=user.admin,
262 admin=user.admin,
263 ldap=user.ldap_dn))
263 ldap=user.ldap_dn))
264
264
265 return dict(id=users_group.users_group_id,
265 return dict(id=users_group.users_group_id,
266 group_name=users_group.users_group_name,
266 group_name=users_group.users_group_name,
267 active=users_group.users_group_active,
267 active=users_group.users_group_active,
268 members=members)
268 members=members)
269
269
270 @HasPermissionAllDecorator('hg.admin')
270 @HasPermissionAllDecorator('hg.admin')
271 def get_users_groups(self, apiuser):
271 def get_users_groups(self, apiuser):
272 """"
272 """"
273 Get all users groups
273 Get all users groups
274
274
275 :param apiuser:
275 :param apiuser:
276 """
276 """
277
277
278 result = []
278 result = []
279 for users_group in UsersGroup.getAll():
279 for users_group in UsersGroup.getAll():
280 members = []
280 members = []
281 for user in users_group.members:
281 for user in users_group.members:
282 user = user.user
282 user = user.user
283 members.append(dict(id=user.user_id,
283 members.append(dict(id=user.user_id,
284 username=user.username,
284 username=user.username,
285 firstname=user.name,
285 firstname=user.name,
286 lastname=user.lastname,
286 lastname=user.lastname,
287 email=user.email,
287 email=user.email,
288 active=user.active,
288 active=user.active,
289 admin=user.admin,
289 admin=user.admin,
290 ldap=user.ldap_dn))
290 ldap=user.ldap_dn))
291
291
292 result.append(dict(id=users_group.users_group_id,
292 result.append(dict(id=users_group.users_group_id,
293 group_name=users_group.users_group_name,
293 group_name=users_group.users_group_name,
294 active=users_group.users_group_active,
294 active=users_group.users_group_active,
295 members=members))
295 members=members))
296 return result
296 return result
297
297
298 @HasPermissionAllDecorator('hg.admin')
298 @HasPermissionAllDecorator('hg.admin')
299 def create_users_group(self, apiuser, group_name, active=True):
299 def create_users_group(self, apiuser, group_name, active=True):
300 """
300 """
301 Creates an new usergroup
301 Creates an new usergroup
302
302
303 :param group_name:
303 :param group_name:
304 :param active:
304 :param active:
305 """
305 """
306
306
307 if self.get_users_group(apiuser, group_name):
307 if self.get_users_group(apiuser, group_name):
308 raise JSONRPCError("users group %s already exist" % group_name)
308 raise JSONRPCError("users group %s already exist" % group_name)
309
309
310 try:
310 try:
311 ug = UsersGroupModel().create(name=group_name, active=active)
311 ug = UsersGroupModel().create(name=group_name, active=active)
312 Session.commit()
312 Session.commit()
313 return dict(id=ug.users_group_id,
313 return dict(id=ug.users_group_id,
314 msg='created new users group %s' % group_name)
314 msg='created new users group %s' % group_name)
315 except Exception:
315 except Exception:
316 log.error(traceback.format_exc())
316 log.error(traceback.format_exc())
317 raise JSONRPCError('failed to create group %s' % group_name)
317 raise JSONRPCError('failed to create group %s' % group_name)
318
318
319 @HasPermissionAllDecorator('hg.admin')
319 @HasPermissionAllDecorator('hg.admin')
320 def add_user_to_users_group(self, apiuser, group_name, username):
320 def add_user_to_users_group(self, apiuser, group_name, username):
321 """"
321 """"
322 Add a user to a users group
322 Add a user to a users group
323
323
324 :param apiuser:
324 :param apiuser:
325 :param group_name:
325 :param group_name:
326 :param username:
326 :param username:
327 """
327 """
328
328
329 try:
329 try:
330 users_group = UsersGroup.get_by_group_name(group_name)
330 users_group = UsersGroup.get_by_group_name(group_name)
331 if not users_group:
331 if not users_group:
332 raise JSONRPCError('unknown users group %s' % group_name)
332 raise JSONRPCError('unknown users group %s' % group_name)
333
333
334 user = User.get_by_username(username)
334 user = User.get_by_username(username)
335 if user is None:
335 if user is None:
336 raise JSONRPCError('unknown user %s' % username)
336 raise JSONRPCError('unknown user %s' % username)
337
337
338 ugm = UsersGroupModel().add_user_to_group(users_group, user)
338 ugm = UsersGroupModel().add_user_to_group(users_group, user)
339 success = True if ugm != True else False
339 success = True if ugm != True else False
340 msg = 'added member %s to users group %s' % (username, group_name)
340 msg = 'added member %s to users group %s' % (username, group_name)
341 msg = msg if success else 'User is already in that group'
341 msg = msg if success else 'User is already in that group'
342 Session.commit()
342 Session.commit()
343
343
344 return dict(
344 return dict(
345 id=ugm.users_group_member_id if ugm != True else None,
345 id=ugm.users_group_member_id if ugm != True else None,
346 success=success,
346 success=success,
347 msg=msg
347 msg=msg
348 )
348 )
349 except Exception:
349 except Exception:
350 log.error(traceback.format_exc())
350 log.error(traceback.format_exc())
351 raise JSONRPCError('failed to add users group member')
351 raise JSONRPCError('failed to add users group member')
352
352
353 @HasPermissionAllDecorator('hg.admin')
353 @HasPermissionAllDecorator('hg.admin')
354 def remove_user_from_users_group(self, apiuser, group_name, username):
354 def remove_user_from_users_group(self, apiuser, group_name, username):
355 """
355 """
356 Remove user from a group
356 Remove user from a group
357
357
358 :param apiuser
358 :param apiuser
359 :param group_name
359 :param group_name
360 :param username
360 :param username
361 """
361 """
362
362
363 try:
363 try:
364 users_group = UsersGroup.get_by_group_name(group_name)
364 users_group = UsersGroup.get_by_group_name(group_name)
365 if not users_group:
365 if not users_group:
366 raise JSONRPCError('unknown users group %s' % group_name)
366 raise JSONRPCError('unknown users group %s' % group_name)
367
367
368 user = User.get_by_username(username)
368 user = User.get_by_username(username)
369 if user is None:
369 if user is None:
370 raise JSONRPCError('unknown user %s' % username)
370 raise JSONRPCError('unknown user %s' % username)
371
371
372 success = UsersGroupModel().remove_user_from_group(users_group, user)
372 success = UsersGroupModel().remove_user_from_group(users_group, user)
373 msg = 'removed member %s from users group %s' % (username, group_name)
373 msg = 'removed member %s from users group %s' % (username, group_name)
374 msg = msg if success else "User wasn't in group"
374 msg = msg if success else "User wasn't in group"
375 Session.commit()
375 Session.commit()
376 return dict(success=success, msg=msg)
376 return dict(success=success, msg=msg)
377 except Exception:
377 except Exception:
378 log.error(traceback.format_exc())
378 log.error(traceback.format_exc())
379 raise JSONRPCError('failed to remove user from group')
379 raise JSONRPCError('failed to remove user from group')
380
380
381 @HasPermissionAnyDecorator('hg.admin')
381 @HasPermissionAnyDecorator('hg.admin')
382 def get_repo(self, apiuser, repoid):
382 def get_repo(self, apiuser, repoid):
383 """"
383 """"
384 Get repository by name
384 Get repository by name
385
385
386 :param apiuser:
386 :param apiuser:
387 :param repo_name:
387 :param repo_name:
388 """
388 """
389
389
390 repo = RepoModel().get_repo(repoid)
390 repo = RepoModel().get_repo(repoid)
391 if repo is None:
391 if repo is None:
392 raise JSONRPCError('unknown repository %s' % repo)
392 raise JSONRPCError('unknown repository "%s"' % (repo or repoid))
393
393
394 members = []
394 members = []
395 for user in repo.repo_to_perm:
395 for user in repo.repo_to_perm:
396 perm = user.permission.permission_name
396 perm = user.permission.permission_name
397 user = user.user
397 user = user.user
398 members.append(
398 members.append(
399 dict(
399 dict(
400 type="user",
400 type="user",
401 id=user.user_id,
401 id=user.user_id,
402 username=user.username,
402 username=user.username,
403 firstname=user.name,
403 firstname=user.name,
404 lastname=user.lastname,
404 lastname=user.lastname,
405 email=user.email,
405 email=user.email,
406 active=user.active,
406 active=user.active,
407 admin=user.admin,
407 admin=user.admin,
408 ldap=user.ldap_dn,
408 ldap=user.ldap_dn,
409 permission=perm
409 permission=perm
410 )
410 )
411 )
411 )
412 for users_group in repo.users_group_to_perm:
412 for users_group in repo.users_group_to_perm:
413 perm = users_group.permission.permission_name
413 perm = users_group.permission.permission_name
414 users_group = users_group.users_group
414 users_group = users_group.users_group
415 members.append(
415 members.append(
416 dict(
416 dict(
417 type="users_group",
417 type="users_group",
418 id=users_group.users_group_id,
418 id=users_group.users_group_id,
419 name=users_group.users_group_name,
419 name=users_group.users_group_name,
420 active=users_group.users_group_active,
420 active=users_group.users_group_active,
421 permission=perm
421 permission=perm
422 )
422 )
423 )
423 )
424
424
425 return dict(
425 return dict(
426 id=repo.repo_id,
426 id=repo.repo_id,
427 repo_name=repo.repo_name,
427 repo_name=repo.repo_name,
428 type=repo.repo_type,
428 type=repo.repo_type,
429 clone_uri=repo.clone_uri,
429 clone_uri=repo.clone_uri,
430 private=repo.private,
430 private=repo.private,
431 created_on=repo.created_on,
431 created_on=repo.created_on,
432 description=repo.description,
432 description=repo.description,
433 members=members
433 members=members
434 )
434 )
435
435
436 @HasPermissionAnyDecorator('hg.admin')
436 @HasPermissionAnyDecorator('hg.admin')
437 def get_repos(self, apiuser):
437 def get_repos(self, apiuser):
438 """"
438 """"
439 Get all repositories
439 Get all repositories
440
440
441 :param apiuser:
441 :param apiuser:
442 """
442 """
443
443
444 result = []
444 result = []
445 for repo in Repository.getAll():
445 for repo in Repository.getAll():
446 result.append(
446 result.append(
447 dict(
447 dict(
448 id=repo.repo_id,
448 id=repo.repo_id,
449 repo_name=repo.repo_name,
449 repo_name=repo.repo_name,
450 type=repo.repo_type,
450 type=repo.repo_type,
451 clone_uri=repo.clone_uri,
451 clone_uri=repo.clone_uri,
452 private=repo.private,
452 private=repo.private,
453 created_on=repo.created_on,
453 created_on=repo.created_on,
454 description=repo.description,
454 description=repo.description,
455 )
455 )
456 )
456 )
457 return result
457 return result
458
458
459 @HasPermissionAnyDecorator('hg.admin')
459 @HasPermissionAnyDecorator('hg.admin')
460 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
460 def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
461 ret_type='all'):
461 ret_type='all'):
462 """
462 """
463 returns a list of nodes and it's children
463 returns a list of nodes and it's children
464 for a given path at given revision. It's possible to specify ret_type
464 for a given path at given revision. It's possible to specify ret_type
465 to show only files or dirs
465 to show only files or dirs
466
466
467 :param apiuser:
467 :param apiuser:
468 :param repo_name: name of repository
468 :param repo_name: name of repository
469 :param revision: revision for which listing should be done
469 :param revision: revision for which listing should be done
470 :param root_path: path from which start displaying
470 :param root_path: path from which start displaying
471 :param ret_type: return type 'all|files|dirs' nodes
471 :param ret_type: return type 'all|files|dirs' nodes
472 """
472 """
473 try:
473 try:
474 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
474 _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
475 flat=False)
475 flat=False)
476 _map = {
476 _map = {
477 'all': _d + _f,
477 'all': _d + _f,
478 'files': _f,
478 'files': _f,
479 'dirs': _d,
479 'dirs': _d,
480 }
480 }
481 return _map[ret_type]
481 return _map[ret_type]
482 except KeyError:
482 except KeyError:
483 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
483 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
484 except Exception, e:
484 except Exception, e:
485 raise JSONRPCError(e)
485 raise JSONRPCError(e)
486
486
487 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
487 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
488 def create_repo(self, apiuser, repo_name, owner_name, description='',
488 def create_repo(self, apiuser, repo_name, owner_name, description='',
489 repo_type='hg', private=False, clone_uri=None):
489 repo_type='hg', private=False, clone_uri=None):
490 """
490 """
491 Create repository, if clone_url is given it makes a remote clone
491 Create repository, if clone_url is given it makes a remote clone
492
492
493 :param apiuser:
493 :param apiuser:
494 :param repo_name:
494 :param repo_name:
495 :param owner_name:
495 :param owner_name:
496 :param description:
496 :param description:
497 :param repo_type:
497 :param repo_type:
498 :param private:
498 :param private:
499 :param clone_uri:
499 :param clone_uri:
500 """
500 """
501
501
502 try:
502 try:
503 owner = User.get_by_username(owner_name)
503 owner = User.get_by_username(owner_name)
504 if owner is None:
504 if owner is None:
505 raise JSONRPCError('unknown user %s' % owner_name)
505 raise JSONRPCError('unknown user %s' % owner_name)
506
506
507 if Repository.get_by_repo_name(repo_name):
507 if Repository.get_by_repo_name(repo_name):
508 raise JSONRPCError("repo %s already exist" % repo_name)
508 raise JSONRPCError("repo %s already exist" % repo_name)
509
509
510 groups = repo_name.split(Repository.url_sep())
510 groups = repo_name.split(Repository.url_sep())
511 real_name = groups[-1]
511 real_name = groups[-1]
512 # create structure of groups
512 # create structure of groups
513 group = map_groups(repo_name)
513 group = map_groups(repo_name)
514
514
515 repo = RepoModel().create(
515 repo = RepoModel().create(
516 dict(
516 dict(
517 repo_name=real_name,
517 repo_name=real_name,
518 repo_name_full=repo_name,
518 repo_name_full=repo_name,
519 description=description,
519 description=description,
520 private=private,
520 private=private,
521 repo_type=repo_type,
521 repo_type=repo_type,
522 repo_group=group.group_id if group else None,
522 repo_group=group.group_id if group else None,
523 clone_uri=clone_uri
523 clone_uri=clone_uri
524 )
524 )
525 )
525 )
526 Session.commit()
526 Session.commit()
527
527
528 return dict(
528 return dict(
529 id=repo.repo_id,
529 id=repo.repo_id,
530 msg="Created new repository %s" % (repo.repo_name),
530 msg="Created new repository %s" % (repo.repo_name),
531 repo=dict(
531 repo=dict(
532 id=repo.repo_id,
532 id=repo.repo_id,
533 repo_name=repo.repo_name,
533 repo_name=repo.repo_name,
534 type=repo.repo_type,
534 type=repo.repo_type,
535 clone_uri=repo.clone_uri,
535 clone_uri=repo.clone_uri,
536 private=repo.private,
536 private=repo.private,
537 created_on=repo.created_on,
537 created_on=repo.created_on,
538 description=repo.description,
538 description=repo.description,
539 )
539 )
540 )
540 )
541
541
542 except Exception:
542 except Exception:
543 log.error(traceback.format_exc())
543 log.error(traceback.format_exc())
544 raise JSONRPCError('failed to create repository %s' % repo_name)
544 raise JSONRPCError('failed to create repository %s' % repo_name)
545
545
546 @HasPermissionAnyDecorator('hg.admin')
546 @HasPermissionAnyDecorator('hg.admin')
547 def delete_repo(self, apiuser, repo_name):
547 def delete_repo(self, apiuser, repo_name):
548 """
548 """
549 Deletes a given repository
549 Deletes a given repository
550
550
551 :param repo_name:
551 :param repo_name:
552 """
552 """
553 if not Repository.get_by_repo_name(repo_name):
553 if not Repository.get_by_repo_name(repo_name):
554 raise JSONRPCError("repo %s does not exist" % repo_name)
554 raise JSONRPCError("repo %s does not exist" % repo_name)
555 try:
555 try:
556 RepoModel().delete(repo_name)
556 RepoModel().delete(repo_name)
557 Session.commit()
557 Session.commit()
558 return dict(
558 return dict(
559 msg='Deleted repository %s' % repo_name
559 msg='Deleted repository %s' % repo_name
560 )
560 )
561 except Exception:
561 except Exception:
562 log.error(traceback.format_exc())
562 log.error(traceback.format_exc())
563 raise JSONRPCError('failed to delete repository %s' % repo_name)
563 raise JSONRPCError('failed to delete repository %s' % repo_name)
564
564
565 @HasPermissionAnyDecorator('hg.admin')
565 @HasPermissionAnyDecorator('hg.admin')
566 def grant_user_permission(self, apiuser, repo_name, username, perm):
566 def grant_user_permission(self, apiuser, repo_name, username, perm):
567 """
567 """
568 Grant permission for user on given repository, or update existing one
568 Grant permission for user on given repository, or update existing one
569 if found
569 if found
570
570
571 :param repo_name:
571 :param repo_name:
572 :param username:
572 :param username:
573 :param perm:
573 :param perm:
574 """
574 """
575
575
576 try:
576 try:
577 repo = Repository.get_by_repo_name(repo_name)
577 repo = Repository.get_by_repo_name(repo_name)
578 if repo is None:
578 if repo is None:
579 raise JSONRPCError('unknown repository %s' % repo)
579 raise JSONRPCError('unknown repository %s' % repo)
580
580
581 user = User.get_by_username(username)
581 user = User.get_by_username(username)
582 if user is None:
582 if user is None:
583 raise JSONRPCError('unknown user %s' % username)
583 raise JSONRPCError('unknown user %s' % username)
584
584
585 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
585 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
586
586
587 Session.commit()
587 Session.commit()
588 return dict(
588 return dict(
589 msg='Granted perm: %s for user: %s in repo: %s' % (
589 msg='Granted perm: %s for user: %s in repo: %s' % (
590 perm, username, repo_name
590 perm, username, repo_name
591 )
591 )
592 )
592 )
593 except Exception:
593 except Exception:
594 log.error(traceback.format_exc())
594 log.error(traceback.format_exc())
595 raise JSONRPCError(
595 raise JSONRPCError(
596 'failed to edit permission %(repo)s for %(user)s' % dict(
596 'failed to edit permission %(repo)s for %(user)s' % dict(
597 user=username, repo=repo_name
597 user=username, repo=repo_name
598 )
598 )
599 )
599 )
600
600
601 @HasPermissionAnyDecorator('hg.admin')
601 @HasPermissionAnyDecorator('hg.admin')
602 def revoke_user_permission(self, apiuser, repo_name, username):
602 def revoke_user_permission(self, apiuser, repo_name, username):
603 """
603 """
604 Revoke permission for user on given repository
604 Revoke permission for user on given repository
605
605
606 :param repo_name:
606 :param repo_name:
607 :param username:
607 :param username:
608 """
608 """
609
609
610 try:
610 try:
611 repo = Repository.get_by_repo_name(repo_name)
611 repo = Repository.get_by_repo_name(repo_name)
612 if repo is None:
612 if repo is None:
613 raise JSONRPCError('unknown repository %s' % repo)
613 raise JSONRPCError('unknown repository %s' % repo)
614
614
615 user = User.get_by_username(username)
615 user = User.get_by_username(username)
616 if user is None:
616 if user is None:
617 raise JSONRPCError('unknown user %s' % username)
617 raise JSONRPCError('unknown user %s' % username)
618
618
619 RepoModel().revoke_user_permission(repo=repo_name, user=username)
619 RepoModel().revoke_user_permission(repo=repo_name, user=username)
620
620
621 Session.commit()
621 Session.commit()
622 return dict(
622 return dict(
623 msg='Revoked perm for user: %s in repo: %s' % (
623 msg='Revoked perm for user: %s in repo: %s' % (
624 username, repo_name
624 username, repo_name
625 )
625 )
626 )
626 )
627 except Exception:
627 except Exception:
628 log.error(traceback.format_exc())
628 log.error(traceback.format_exc())
629 raise JSONRPCError(
629 raise JSONRPCError(
630 'failed to edit permission %(repo)s for %(user)s' % dict(
630 'failed to edit permission %(repo)s for %(user)s' % dict(
631 user=username, repo=repo_name
631 user=username, repo=repo_name
632 )
632 )
633 )
633 )
634
634
635 @HasPermissionAnyDecorator('hg.admin')
635 @HasPermissionAnyDecorator('hg.admin')
636 def grant_users_group_permission(self, apiuser, repo_name, group_name, perm):
636 def grant_users_group_permission(self, apiuser, repo_name, group_name, perm):
637 """
637 """
638 Grant permission for users group on given repository, or update
638 Grant permission for users group on given repository, or update
639 existing one if found
639 existing one if found
640
640
641 :param repo_name:
641 :param repo_name:
642 :param group_name:
642 :param group_name:
643 :param perm:
643 :param perm:
644 """
644 """
645
645
646 try:
646 try:
647 repo = Repository.get_by_repo_name(repo_name)
647 repo = Repository.get_by_repo_name(repo_name)
648 if repo is None:
648 if repo is None:
649 raise JSONRPCError('unknown repository %s' % repo)
649 raise JSONRPCError('unknown repository %s' % repo)
650
650
651 user_group = UsersGroup.get_by_group_name(group_name)
651 user_group = UsersGroup.get_by_group_name(group_name)
652 if user_group is None:
652 if user_group is None:
653 raise JSONRPCError('unknown users group %s' % user_group)
653 raise JSONRPCError('unknown users group %s' % user_group)
654
654
655 RepoModel().grant_users_group_permission(repo=repo_name,
655 RepoModel().grant_users_group_permission(repo=repo_name,
656 group_name=group_name,
656 group_name=group_name,
657 perm=perm)
657 perm=perm)
658
658
659 Session.commit()
659 Session.commit()
660 return dict(
660 return dict(
661 msg='Granted perm: %s for group: %s in repo: %s' % (
661 msg='Granted perm: %s for group: %s in repo: %s' % (
662 perm, group_name, repo_name
662 perm, group_name, repo_name
663 )
663 )
664 )
664 )
665 except Exception:
665 except Exception:
666 log.error(traceback.format_exc())
666 log.error(traceback.format_exc())
667 raise JSONRPCError(
667 raise JSONRPCError(
668 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
668 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
669 usersgr=group_name, repo=repo_name
669 usersgr=group_name, repo=repo_name
670 )
670 )
671 )
671 )
672
672
673 @HasPermissionAnyDecorator('hg.admin')
673 @HasPermissionAnyDecorator('hg.admin')
674 def revoke_users_group_permission(self, apiuser, repo_name, group_name):
674 def revoke_users_group_permission(self, apiuser, repo_name, group_name):
675 """
675 """
676 Revoke permission for users group on given repository
676 Revoke permission for users group on given repository
677
677
678 :param repo_name:
678 :param repo_name:
679 :param group_name:
679 :param group_name:
680 """
680 """
681
681
682 try:
682 try:
683 repo = Repository.get_by_repo_name(repo_name)
683 repo = Repository.get_by_repo_name(repo_name)
684 if repo is None:
684 if repo is None:
685 raise JSONRPCError('unknown repository %s' % repo)
685 raise JSONRPCError('unknown repository %s' % repo)
686
686
687 user_group = UsersGroup.get_by_group_name(group_name)
687 user_group = UsersGroup.get_by_group_name(group_name)
688 if user_group is None:
688 if user_group is None:
689 raise JSONRPCError('unknown users group %s' % user_group)
689 raise JSONRPCError('unknown users group %s' % user_group)
690
690
691 RepoModel().revoke_users_group_permission(repo=repo_name,
691 RepoModel().revoke_users_group_permission(repo=repo_name,
692 group_name=group_name)
692 group_name=group_name)
693
693
694 Session.commit()
694 Session.commit()
695 return dict(
695 return dict(
696 msg='Revoked perm for group: %s in repo: %s' % (
696 msg='Revoked perm for group: %s in repo: %s' % (
697 group_name, repo_name
697 group_name, repo_name
698 )
698 )
699 )
699 )
700 except Exception:
700 except Exception:
701 log.error(traceback.format_exc())
701 log.error(traceback.format_exc())
702 raise JSONRPCError(
702 raise JSONRPCError(
703 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
703 'failed to edit permission %(repo)s for %(usersgr)s' % dict(
704 usersgr=group_name, repo=repo_name
704 usersgr=group_name, repo=repo_name
705 )
705 )
706 )
706 )
@@ -1,54 +1,54 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 %if c.users_log:
2 %if c.users_log:
3 <table>
3 <table>
4 <tr>
4 <tr>
5 <th class="left">${_('Username')}</th>
5 <th class="left">${_('Username')}</th>
6 <th class="left">${_('Action')}</th>
6 <th class="left">${_('Action')}</th>
7 <th class="left">${_('Repository')}</th>
7 <th class="left">${_('Repository')}</th>
8 <th class="left">${_('Date')}</th>
8 <th class="left">${_('Date')}</th>
9 <th class="left">${_('From IP')}</th>
9 <th class="left">${_('From IP')}</th>
10 </tr>
10 </tr>
11
11
12 %for cnt,l in enumerate(c.users_log):
12 %for cnt,l in enumerate(c.users_log):
13 <tr class="parity${cnt%2}">
13 <tr class="parity${cnt%2}">
14 <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td>
14 <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td>
15 <td>${h.action_parser(l)[0]()}
15 <td>${h.action_parser(l)[0]()}
16 <div class="journal_action_params">
16 <div class="journal_action_params">
17 ${h.literal(h.action_parser(l)[1]())}
17 ${h.literal(h.action_parser(l)[1]())}
18 </div>
18 </div>
19 </td>
19 </td>
20 <td>
20 <td>
21 %if l.repository is not None:
21 %if l.repository is not None:
22 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
22 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
23 %else:
23 %else:
24 ${l.repository_name}
24 ${l.repository_name}
25 %endif
25 %endif
26 </td>
26 </td>
27
27
28 <td>${l.action_date}</td>
28 <td>${l.action_date}</td>
29 <td>${l.user_ip}</td>
29 <td>${l.user_ip}</td>
30 </tr>
30 </tr>
31 %endfor
31 %endfor
32 </table>
32 </table>
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
35 YUE.onDOMReady(function(){
35 YUE.onDOMReady(function(){
36 YUE.delegate("user_log","click",function(e, matchedEl, container){
36 YUE.delegate("user_log","click",function(e, matchedEl, container){
37 ypjax(e.target.href,"user_log",function(){show_more_event();tooltip_activate();});
37 ypjax(e.target.href,"user_log",function(){show_more_event();tooltip_activate();});
38 YUE.preventDefault(e);
38 YUE.preventDefault(e);
39 },'.pager_link');
39 },'.pager_link');
40
40
41 YUE.delegate("user_log","click",function(e,matchedEl,container){
41 YUE.delegate("user_log","click",function(e,matchedEl,container){
42 var el = e.target;
42 var el = e.target;
43 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
43 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
44 YUD.setStyle(el.parentNode,'display','none');
44 YUD.setStyle(el.parentNode,'display','none');
45 },'.show_more');
45 },'.show_more');
46 });
46 });
47 </script>
47 </script>
48
48
49 <div class="pagination-wh pagination-left">
49 <div class="pagination-wh pagination-left">
50 ${c.users_log.pager('$link_previous ~2~ $link_next')}
50 ${c.users_log.pager('$link_previous ~2~ $link_next')}
51 </div>
51 </div>
52 %else:
52 %else:
53 ${_('No actions yet')}
53 ${_('No actions yet')}
54 %endif
54 %endif
@@ -1,103 +1,106 b''
1 import sys
1 import sys
2 from rhodecode import get_version
2 from rhodecode import get_version
3 from rhodecode import __license__
3 from rhodecode import __license__
4 from rhodecode import __py_version__
4 from rhodecode import __py_version__
5 from rhodecode import requirements
5 from rhodecode import requirements
6
6
7 if __py_version__ < (2, 5):
7 if __py_version__ < (2, 5):
8 raise Exception('RhodeCode requires python 2.5 or later')
8 raise Exception('RhodeCode requires python 2.5 or later')
9
9
10 dependency_links = [
10 dependency_links = [
11 ]
11 ]
12
12
13 classifiers = [
13 classifiers = [
14 'Development Status :: 4 - Beta',
14 'Development Status :: 4 - Beta',
15 'Environment :: Web Environment',
15 'Environment :: Web Environment',
16 'Framework :: Pylons',
16 'Framework :: Pylons',
17 'Intended Audience :: Developers',
17 'Intended Audience :: Developers',
18 'License :: OSI Approved :: GNU General Public License (GPL)',
18 'License :: OSI Approved :: GNU General Public License (GPL)',
19 'Operating System :: OS Independent',
19 'Operating System :: OS Independent',
20 'Programming Language :: Python',
20 'Programming Language :: Python',
21 'Programming Language :: Python :: 2.5',
21 'Programming Language :: Python :: 2.5',
22 'Programming Language :: Python :: 2.6',
22 'Programming Language :: Python :: 2.6',
23 'Programming Language :: Python :: 2.7',
23 'Programming Language :: Python :: 2.7',
24 ]
24 ]
25
25
26
26
27 # additional files from project that goes somewhere in the filesystem
27 # additional files from project that goes somewhere in the filesystem
28 # relative to sys.prefix
28 # relative to sys.prefix
29 data_files = []
29 data_files = []
30
30
31 # additional files that goes into package itself
31 # additional files that goes into package itself
32 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
32 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
33
33
34 description = ('Mercurial repository browser/management with '
34 description = ('Mercurial repository browser/management with '
35 'build in push/pull server and full text search')
35 'build in push/pull server and full text search')
36 keywords = ' '.join(['rhodecode', 'rhodiumcode', 'mercurial', 'git',
36 keywords = ' '.join(['rhodecode', 'rhodiumcode', 'mercurial', 'git',
37 'code review', 'repo groups', 'ldap'
37 'code review', 'repo groups', 'ldap'
38 'repository management', 'hgweb replacement'
38 'repository management', 'hgweb replacement'
39 'hgwebdir', 'gitweb replacement', 'serving hgweb', ])
39 'hgwebdir', 'gitweb replacement', 'serving hgweb', ])
40 # long description
40 # long description
41 try:
41 try:
42 readme_file = 'README.rst'
42 readme_file = 'README.rst'
43 changelog_file = 'docs/changelog.rst'
43 changelog_file = 'docs/changelog.rst'
44 long_description = open(readme_file).read() + '\n\n' + \
44 long_description = open(readme_file).read() + '\n\n' + \
45 open(changelog_file).read()
45 open(changelog_file).read()
46
46
47 except IOError, err:
47 except IOError, err:
48 sys.stderr.write("[WARNING] Cannot find file specified as "
48 sys.stderr.write("[WARNING] Cannot find file specified as "
49 "long_description (%s)\n or changelog (%s) skipping that file" \
49 "long_description (%s)\n or changelog (%s) skipping that file" \
50 % (readme_file, changelog_file))
50 % (readme_file, changelog_file))
51 long_description = description
51 long_description = description
52
52
53
53
54 try:
54 try:
55 from setuptools import setup, find_packages
55 from setuptools import setup, find_packages
56 except ImportError:
56 except ImportError:
57 from ez_setup import use_setuptools
57 from ez_setup import use_setuptools
58 use_setuptools()
58 use_setuptools()
59 from setuptools import setup, find_packages
59 from setuptools import setup, find_packages
60 # packages
60 # packages
61 packages = find_packages(exclude=['ez_setup'])
61 packages = find_packages(exclude=['ez_setup'])
62
62
63 setup(
63 setup(
64 name='RhodeCode',
64 name='RhodeCode',
65 version=get_version(),
65 version=get_version(),
66 description=description,
66 description=description,
67 long_description=long_description,
67 long_description=long_description,
68 keywords=keywords,
68 keywords=keywords,
69 license=__license__,
69 license=__license__,
70 author='Marcin Kuzminski',
70 author='Marcin Kuzminski',
71 author_email='marcin@python-works.com',
71 author_email='marcin@python-works.com',
72 dependency_links=dependency_links,
72 dependency_links=dependency_links,
73 url='http://rhodecode.org',
73 url='http://rhodecode.org',
74 install_requires=requirements,
74 install_requires=requirements,
75 classifiers=classifiers,
75 classifiers=classifiers,
76 setup_requires=["PasteScript>=1.6.3"],
76 setup_requires=["PasteScript>=1.6.3"],
77 data_files=data_files,
77 data_files=data_files,
78 packages=packages,
78 packages=packages,
79 include_package_data=True,
79 include_package_data=True,
80 test_suite='nose.collector',
80 test_suite='nose.collector',
81 package_data=package_data,
81 package_data=package_data,
82 message_extractors={'rhodecode': [
82 message_extractors={'rhodecode': [
83 ('**.py', 'python', None),
83 ('**.py', 'python', None),
84 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
84 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
85 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
85 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
86 ('public/**', 'ignore', None)]},
86 ('public/**', 'ignore', None)]},
87 zip_safe=False,
87 zip_safe=False,
88 paster_plugins=['PasteScript', 'Pylons'],
88 paster_plugins=['PasteScript', 'Pylons'],
89 entry_points="""
89 entry_points="""
90 [console_scripts]
91 rhodecode-api = rhodecode.bin.rhodecode_api:main
92
90 [paste.app_factory]
93 [paste.app_factory]
91 main = rhodecode.config.middleware:make_app
94 main = rhodecode.config.middleware:make_app
92
95
93 [paste.app_install]
96 [paste.app_install]
94 main = pylons.util:PylonsInstaller
97 main = pylons.util:PylonsInstaller
95
98
96 [paste.global_paster_command]
99 [paste.global_paster_command]
97 setup-rhodecode=rhodecode.config.setup_rhodecode:SetupCommand
100 setup-rhodecode=rhodecode.config.setup_rhodecode:SetupCommand
98 make-index=rhodecode.lib.indexers:MakeIndex
101 make-index=rhodecode.lib.indexers:MakeIndex
99 make-rcext=rhodecode.config.rcextensions.make_rcextensions:MakeRcExt
102 make-rcext=rhodecode.config.rcextensions.make_rcextensions:MakeRcExt
100 upgrade-db=rhodecode.lib.dbmigrate:UpgradeDb
103 upgrade-db=rhodecode.lib.dbmigrate:UpgradeDb
101 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
104 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
102 """,
105 """,
103 )
106 )
General Comments 0
You need to be logged in to leave comments. Login now