##// END OF EJS Templates
fixed issues with python2.5...
marcink -
r1514:87ec80c2 beta
parent child Browse files
Show More
@@ -0,0 +1,360 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.compat
4 ~~~~~~~~~~~~~~~~~~~~
5
6 Python backward compatibility functions and common libs
7
8
9 :created_on: Oct 7, 2011
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
13 """
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
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
23 #
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/>.
26
27 #==============================================================================
28 # json
29 #==============================================================================
30 try:
31 import json
32 except ImportError:
33 import simplejson as json
34
35
36 #==============================================================================
37 # izip_longest
38 #==============================================================================
39 try:
40 from itertools import izip_longest
41 except ImportError:
42 import itertools
43
44 def izip_longest(*args, **kwds): # noqa
45 fillvalue = kwds.get("fillvalue")
46
47 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
48 yield counter() # yields the fillvalue, or raises IndexError
49
50 fillers = itertools.repeat(fillvalue)
51 iters = [itertools.chain(it, sentinel(), fillers)
52 for it in args]
53 try:
54 for tup in itertools.izip(*iters):
55 yield tup
56 except IndexError:
57 pass
58
59
60 #==============================================================================
61 # OrderedDict
62 #==============================================================================
63
64 # Python Software Foundation License
65
66 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
67 # "!=" should be faster.
68 class _Nil(object):
69
70 def __repr__(self):
71 return "nil"
72
73 def __eq__(self, other):
74 if (isinstance(other, _Nil)):
75 return True
76 else:
77 return NotImplemented
78
79 def __ne__(self, other):
80 if (isinstance(other, _Nil)):
81 return False
82 else:
83 return NotImplemented
84
85 _nil = _Nil()
86
87 class _odict(object):
88 """Ordered dict data structure, with O(1) complexity for dict operations
89 that modify one element.
90
91 Overwriting values doesn't change their original sequential order.
92 """
93
94 def _dict_impl(self):
95 return None
96
97 def __init__(self, data=(), **kwds):
98 """This doesn't accept keyword initialization as normal dicts to avoid
99 a trap - inside a function or method the keyword args are accessible
100 only as a dict, without a defined order, so their original order is
101 lost.
102 """
103 if kwds:
104 raise TypeError("__init__() of ordered dict takes no keyword "
105 "arguments to avoid an ordering trap.")
106 self._dict_impl().__init__(self)
107 # If you give a normal dict, then the order of elements is undefined
108 if hasattr(data, "iteritems"):
109 for key, val in data.iteritems():
110 self[key] = val
111 else:
112 for key, val in data:
113 self[key] = val
114
115 # Double-linked list header
116 def _get_lh(self):
117 dict_impl = self._dict_impl()
118 if not hasattr(self, '_lh'):
119 dict_impl.__setattr__(self, '_lh', _nil)
120 return dict_impl.__getattribute__(self, '_lh')
121
122 def _set_lh(self, val):
123 self._dict_impl().__setattr__(self, '_lh', val)
124
125 lh = property(_get_lh, _set_lh)
126
127 # Double-linked list tail
128 def _get_lt(self):
129 dict_impl = self._dict_impl()
130 if not hasattr(self, '_lt'):
131 dict_impl.__setattr__(self, '_lt', _nil)
132 return dict_impl.__getattribute__(self, '_lt')
133
134 def _set_lt(self, val):
135 self._dict_impl().__setattr__(self, '_lt', val)
136
137 lt = property(_get_lt, _set_lt)
138
139 def __getitem__(self, key):
140 return self._dict_impl().__getitem__(self, key)[1]
141
142 def __setitem__(self, key, val):
143 dict_impl = self._dict_impl()
144 try:
145 dict_impl.__getitem__(self, key)[1] = val
146 except KeyError, e:
147 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
148 dict_impl.__setitem__(self, key, new)
149 if dict_impl.__getattribute__(self, 'lt') == _nil:
150 dict_impl.__setattr__(self, 'lh', key)
151 else:
152 dict_impl.__getitem__(
153 self, dict_impl.__getattribute__(self, 'lt'))[2] = key
154 dict_impl.__setattr__(self, 'lt', key)
155
156 def __delitem__(self, key):
157 dict_impl = self._dict_impl()
158 pred, _ , succ = self._dict_impl().__getitem__(self, key)
159 if pred == _nil:
160 dict_impl.__setattr__(self, 'lh', succ)
161 else:
162 dict_impl.__getitem__(self, pred)[2] = succ
163 if succ == _nil:
164 dict_impl.__setattr__(self, 'lt', pred)
165 else:
166 dict_impl.__getitem__(self, succ)[0] = pred
167 dict_impl.__delitem__(self, key)
168
169 def __contains__(self, key):
170 return key in self.keys()
171
172 def __len__(self):
173 return len(self.keys())
174
175 def __str__(self):
176 pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
177 return "{%s}" % ", ".join(pairs)
178
179 def __repr__(self):
180 if self:
181 pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
182 return "odict([%s])" % ", ".join(pairs)
183 else:
184 return "odict()"
185
186 def get(self, k, x=None):
187 if k in self:
188 return self._dict_impl().__getitem__(self, k)[1]
189 else:
190 return x
191
192 def __iter__(self):
193 dict_impl = self._dict_impl()
194 curr_key = dict_impl.__getattribute__(self, 'lh')
195 while curr_key != _nil:
196 yield curr_key
197 curr_key = dict_impl.__getitem__(self, curr_key)[2]
198
199 iterkeys = __iter__
200
201 def keys(self):
202 return list(self.iterkeys())
203
204 def itervalues(self):
205 dict_impl = self._dict_impl()
206 curr_key = dict_impl.__getattribute__(self, 'lh')
207 while curr_key != _nil:
208 _, val, curr_key = dict_impl.__getitem__(self, curr_key)
209 yield val
210
211 def values(self):
212 return list(self.itervalues())
213
214 def iteritems(self):
215 dict_impl = self._dict_impl()
216 curr_key = dict_impl.__getattribute__(self, 'lh')
217 while curr_key != _nil:
218 _, val, next_key = dict_impl.__getitem__(self, curr_key)
219 yield curr_key, val
220 curr_key = next_key
221
222 def items(self):
223 return list(self.iteritems())
224
225 def sort(self, cmp=None, key=None, reverse=False):
226 items = [(k, v) for k, v in self.items()]
227 if cmp is not None:
228 items = sorted(items, cmp=cmp)
229 elif key is not None:
230 items = sorted(items, key=key)
231 else:
232 items = sorted(items, key=lambda x: x[1])
233 if reverse:
234 items.reverse()
235 self.clear()
236 self.__init__(items)
237
238 def clear(self):
239 dict_impl = self._dict_impl()
240 dict_impl.clear(self)
241 dict_impl.__setattr__(self, 'lh', _nil)
242 dict_impl.__setattr__(self, 'lt', _nil)
243
244 def copy(self):
245 return self.__class__(self)
246
247 def update(self, data=(), **kwds):
248 if kwds:
249 raise TypeError("update() of ordered dict takes no keyword "
250 "arguments to avoid an ordering trap.")
251 if hasattr(data, "iteritems"):
252 data = data.iteritems()
253 for key, val in data:
254 self[key] = val
255
256 def setdefault(self, k, x=None):
257 try:
258 return self[k]
259 except KeyError:
260 self[k] = x
261 return x
262
263 def pop(self, k, x=_nil):
264 try:
265 val = self[k]
266 del self[k]
267 return val
268 except KeyError:
269 if x == _nil:
270 raise
271 return x
272
273 def popitem(self):
274 try:
275 dict_impl = self._dict_impl()
276 key = dict_impl.__getattribute__(self, 'lt')
277 return key, self.pop(key)
278 except KeyError:
279 raise KeyError("'popitem(): ordered dictionary is empty'")
280
281 def riterkeys(self):
282 """To iterate on keys in reversed order.
283 """
284 dict_impl = self._dict_impl()
285 curr_key = dict_impl.__getattribute__(self, 'lt')
286 while curr_key != _nil:
287 yield curr_key
288 curr_key = dict_impl.__getitem__(self, curr_key)[0]
289
290 __reversed__ = riterkeys
291
292 def rkeys(self):
293 """List of the keys in reversed order.
294 """
295 return list(self.riterkeys())
296
297 def ritervalues(self):
298 """To iterate on values in reversed order.
299 """
300 dict_impl = self._dict_impl()
301 curr_key = dict_impl.__getattribute__(self, 'lt')
302 while curr_key != _nil:
303 curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
304 yield val
305
306 def rvalues(self):
307 """List of the values in reversed order.
308 """
309 return list(self.ritervalues())
310
311 def riteritems(self):
312 """To iterate on (key, value) in reversed order.
313 """
314 dict_impl = self._dict_impl()
315 curr_key = dict_impl.__getattribute__(self, 'lt')
316 while curr_key != _nil:
317 pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
318 yield curr_key, val
319 curr_key = pred_key
320
321 def ritems(self):
322 """List of the (key, value) in reversed order.
323 """
324 return list(self.riteritems())
325
326 def firstkey(self):
327 if self:
328 return self._dict_impl().__getattribute__(self, 'lh')
329 else:
330 raise KeyError("'firstkey(): ordered dictionary is empty'")
331
332 def lastkey(self):
333 if self:
334 return self._dict_impl().__getattribute__(self, 'lt')
335 else:
336 raise KeyError("'lastkey(): ordered dictionary is empty'")
337
338 def as_dict(self):
339 return self._dict_impl()(self.items())
340
341 def _repr(self):
342 """_repr(): low level repr of the whole data contained in the odict.
343 Useful for debugging.
344 """
345 dict_impl = self._dict_impl()
346 form = "odict low level repr lh,lt,data: %r, %r, %s"
347 return form % (dict_impl.__getattribute__(self, 'lh'),
348 dict_impl.__getattribute__(self, 'lt'),
349 dict_impl.__repr__(self))
350
351 class OrderedDict(_odict, dict):
352
353 def _dict_impl(self):
354 return dict
355
356
357 #==============================================================================
358 # OrderedSet
359 #==============================================================================
360 from sqlalchemy.util import OrderedSet
@@ -1,241 +1,242 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) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 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 json
30 import logging
29 import logging
31 import types
30 import types
32 import urllib
31 import urllib
33 import traceback
32 import traceback
34 from itertools import izip_longest
33
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 from pylons.controllers.util import Response
39 from pylons.controllers.util import Response
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 class JSONRPCError(BaseException):
49 class JSONRPCError(BaseException):
50
50
51 def __init__(self, message):
51 def __init__(self, message):
52 self.message = message
52 self.message = message
53
53
54 def __str__(self):
54 def __str__(self):
55 return str(self.message)
55 return str(self.message)
56
56
57
57
58 def jsonrpc_error(message, code=None):
58 def jsonrpc_error(message, code=None):
59 """Generate a Response object with a JSON-RPC error body"""
59 """Generate a Response object with a JSON-RPC error body"""
60 return Response(body=json.dumps(dict(result=None,
60 return Response(body=json.dumps(dict(result=None,
61 error=message)))
61 error=message)))
62
62
63
63
64 class JSONRPCController(WSGIController):
64 class JSONRPCController(WSGIController):
65 """
65 """
66 A WSGI-speaking JSON-RPC controller class
66 A WSGI-speaking JSON-RPC controller class
67
67
68 See the specification:
68 See the specification:
69 <http://json-rpc.org/wiki/specification>`.
69 <http://json-rpc.org/wiki/specification>`.
70
70
71 Valid controller return values should be json-serializable objects.
71 Valid controller return values should be json-serializable objects.
72
72
73 Sub-classes should catch their exceptions and raise JSONRPCError
73 Sub-classes should catch their exceptions and raise JSONRPCError
74 if they want to pass meaningful errors to the client.
74 if they want to pass meaningful errors to the client.
75
75
76 """
76 """
77
77
78 def _get_method_args(self):
78 def _get_method_args(self):
79 """
79 """
80 Return `self._rpc_args` to dispatched controller method
80 Return `self._rpc_args` to dispatched controller method
81 chosen by __call__
81 chosen by __call__
82 """
82 """
83 return self._rpc_args
83 return self._rpc_args
84
84
85 def __call__(self, environ, start_response):
85 def __call__(self, environ, start_response):
86 """
86 """
87 Parse the request body as JSON, look up the method on the
87 Parse the request body as JSON, look up the method on the
88 controller and if it exists, dispatch to it.
88 controller and if it exists, dispatch to it.
89 """
89 """
90 if 'CONTENT_LENGTH' not in environ:
90 if 'CONTENT_LENGTH' not in environ:
91 log.debug("No Content-Length")
91 log.debug("No Content-Length")
92 return jsonrpc_error(message="No Content-Length in request")
92 return jsonrpc_error(message="No Content-Length in request")
93 else:
93 else:
94 length = environ['CONTENT_LENGTH'] or 0
94 length = environ['CONTENT_LENGTH'] or 0
95 length = int(environ['CONTENT_LENGTH'])
95 length = int(environ['CONTENT_LENGTH'])
96 log.debug('Content-Length: %s', length)
96 log.debug('Content-Length: %s', length)
97
97
98 if length == 0:
98 if length == 0:
99 log.debug("Content-Length is 0")
99 log.debug("Content-Length is 0")
100 return jsonrpc_error(message="Content-Length is 0")
100 return jsonrpc_error(message="Content-Length is 0")
101
101
102 raw_body = environ['wsgi.input'].read(length)
102 raw_body = environ['wsgi.input'].read(length)
103
103
104 try:
104 try:
105 json_body = json.loads(urllib.unquote_plus(raw_body))
105 json_body = json.loads(urllib.unquote_plus(raw_body))
106 except ValueError as e:
106 except ValueError, e:
107 #catch JSON errors Here
107 #catch JSON errors Here
108 return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
108 return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
109 % (e, urllib.unquote_plus(raw_body)))
109 % (e, urllib.unquote_plus(raw_body)))
110
110
111 #check AUTH based on API KEY
111 #check AUTH based on API KEY
112 try:
112 try:
113 self._req_api_key = json_body['api_key']
113 self._req_api_key = json_body['api_key']
114 self._req_method = json_body['method']
114 self._req_method = json_body['method']
115 self._req_params = json_body['args']
115 self._req_params = json_body['args']
116 log.debug('method: %s, params: %s',
116 log.debug('method: %s, params: %s',
117 self._req_method,
117 self._req_method,
118 self._req_params)
118 self._req_params)
119 except KeyError as e:
119 except KeyError, e:
120 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
120 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
121
121
122 #check if we can find this session using api_key
122 #check if we can find this session using api_key
123 try:
123 try:
124 u = User.get_by_api_key(self._req_api_key)
124 u = User.get_by_api_key(self._req_api_key)
125 auth_u = AuthUser(u.user_id, self._req_api_key)
125 auth_u = AuthUser(u.user_id, self._req_api_key)
126 except Exception as e:
126 except Exception, e:
127 return jsonrpc_error(message='Invalid API KEY')
127 return jsonrpc_error(message='Invalid API KEY')
128
128
129 self._error = None
129 self._error = None
130 try:
130 try:
131 self._func = self._find_method()
131 self._func = self._find_method()
132 except AttributeError, e:
132 except AttributeError, e:
133 return jsonrpc_error(message=str(e))
133 return jsonrpc_error(message=str(e))
134
134
135 # now that we have a method, add self._req_params to
135 # now that we have a method, add self._req_params to
136 # self.kargs and dispatch control to WGIController
136 # self.kargs and dispatch control to WGIController
137 argspec = inspect.getargspec(self._func)
137 argspec = inspect.getargspec(self._func)
138 arglist = argspec[0][1:]
138 arglist = argspec[0][1:]
139 defaults = argspec[3] or []
139 defaults = argspec[3] or []
140 default_empty = types.NotImplementedType
140 default_empty = types.NotImplementedType
141
141
142 kwarglist = list(izip_longest(reversed(arglist),reversed(defaults),
142 kwarglist = list(izip_longest(reversed(arglist), reversed(defaults),
143 fillvalue=default_empty))
143 fillvalue=default_empty))
144
144
145 # this is little trick to inject logged in user for
145 # this is little trick to inject logged in user for
146 # perms decorators to work they expect the controller class to have
146 # perms decorators to work they expect the controller class to have
147 # rhodecode_user attribute set
147 # rhodecode_user attribute set
148 self.rhodecode_user = auth_u
148 self.rhodecode_user = auth_u
149
149
150 # This attribute will need to be first param of a method that uses
150 # This attribute will need to be first param of a method that uses
151 # api_key, which is translated to instance of user at that name
151 # api_key, which is translated to instance of user at that name
152 USER_SESSION_ATTR = 'apiuser'
152 USER_SESSION_ATTR = 'apiuser'
153
153
154 if USER_SESSION_ATTR not in arglist:
154 if USER_SESSION_ATTR not in arglist:
155 return jsonrpc_error(message='This method [%s] does not support '
155 return jsonrpc_error(message='This method [%s] does not support '
156 'authentication (missing %s param)' %
156 'authentication (missing %s param)' %
157 (self._func.__name__, USER_SESSION_ATTR))
157 (self._func.__name__, USER_SESSION_ATTR))
158
158
159 # get our arglist and check if we provided them as args
159 # get our arglist and check if we provided them as args
160 for arg,default in kwarglist:
160 for arg, default in kwarglist:
161 if arg == USER_SESSION_ATTR:
161 if arg == USER_SESSION_ATTR:
162 # USER_SESSION_ATTR is something translated from api key and
162 # USER_SESSION_ATTR is something translated from api key and
163 # this is checked before so we don't need validate it
163 # this is checked before so we don't need validate it
164 continue
164 continue
165
165
166 # skip the required param check if it's default value is
166 # skip the required param check if it's default value is
167 # NotImplementedType (default_empty)
167 # NotImplementedType (default_empty)
168 if not self._req_params or (type(default) == default_empty
168 if not self._req_params or (type(default) == default_empty
169 and arg not in self._req_params):
169 and arg not in self._req_params):
170 return jsonrpc_error(message=('Missing non optional %s arg '
170 return jsonrpc_error(message=('Missing non optional %s arg '
171 'in JSON DATA') % arg)
171 'in JSON DATA') % arg)
172
172
173 self._rpc_args = {USER_SESSION_ATTR:u}
173 self._rpc_args = {USER_SESSION_ATTR:u}
174 self._rpc_args.update(self._req_params)
174 self._rpc_args.update(self._req_params)
175
175
176 self._rpc_args['action'] = self._req_method
176 self._rpc_args['action'] = self._req_method
177 self._rpc_args['environ'] = environ
177 self._rpc_args['environ'] = environ
178 self._rpc_args['start_response'] = start_response
178 self._rpc_args['start_response'] = start_response
179
179
180 status = []
180 status = []
181 headers = []
181 headers = []
182 exc_info = []
182 exc_info = []
183 def change_content(new_status, new_headers, new_exc_info=None):
183 def change_content(new_status, new_headers, new_exc_info=None):
184 status.append(new_status)
184 status.append(new_status)
185 headers.extend(new_headers)
185 headers.extend(new_headers)
186 exc_info.append(new_exc_info)
186 exc_info.append(new_exc_info)
187
187
188 output = WSGIController.__call__(self, environ, change_content)
188 output = WSGIController.__call__(self, environ, change_content)
189 output = list(output)
189 output = list(output)
190 headers.append(('Content-Length', str(len(output[0]))))
190 headers.append(('Content-Length', str(len(output[0]))))
191 replace_header(headers, 'Content-Type', 'application/json')
191 replace_header(headers, 'Content-Type', 'application/json')
192 start_response(status[0], headers, exc_info[0])
192 start_response(status[0], headers, exc_info[0])
193
193
194 return output
194 return output
195
195
196 def _dispatch_call(self):
196 def _dispatch_call(self):
197 """
197 """
198 Implement dispatch interface specified by WSGIController
198 Implement dispatch interface specified by WSGIController
199 """
199 """
200 try:
200 try:
201 raw_response = self._inspect_call(self._func)
201 raw_response = self._inspect_call(self._func)
202 if isinstance(raw_response, HTTPError):
202 if isinstance(raw_response, HTTPError):
203 self._error = str(raw_response)
203 self._error = str(raw_response)
204 except JSONRPCError as e:
204 except JSONRPCError, e:
205 self._error = str(e)
205 self._error = str(e)
206 except Exception as e:
206 except Exception, e:
207 log.error('Encountered unhandled exception: %s' % traceback.format_exc())
207 log.error('Encountered unhandled exception: %s' \
208 % traceback.format_exc())
208 json_exc = JSONRPCError('Internal server error')
209 json_exc = JSONRPCError('Internal server error')
209 self._error = str(json_exc)
210 self._error = str(json_exc)
210
211
211 if self._error is not None:
212 if self._error is not None:
212 raw_response = None
213 raw_response = None
213
214
214 response = dict(result=raw_response, error=self._error)
215 response = dict(result=raw_response, error=self._error)
215
216
216 try:
217 try:
217 return json.dumps(response)
218 return json.dumps(response)
218 except TypeError, e:
219 except TypeError, e:
219 log.debug('Error encoding response: %s', e)
220 log.debug('Error encoding response: %s', e)
220 return json.dumps(dict(result=None,
221 return json.dumps(dict(result=None,
221 error="Error encoding response"))
222 error="Error encoding response"))
222
223
223 def _find_method(self):
224 def _find_method(self):
224 """
225 """
225 Return method named by `self._req_method` in controller if able
226 Return method named by `self._req_method` in controller if able
226 """
227 """
227 log.debug('Trying to find JSON-RPC method: %s', self._req_method)
228 log.debug('Trying to find JSON-RPC method: %s', self._req_method)
228 if self._req_method.startswith('_'):
229 if self._req_method.startswith('_'):
229 raise AttributeError("Method not allowed")
230 raise AttributeError("Method not allowed")
230
231
231 try:
232 try:
232 func = getattr(self, self._req_method, None)
233 func = getattr(self, self._req_method, None)
233 except UnicodeEncodeError:
234 except UnicodeEncodeError:
234 raise AttributeError("Problem decoding unicode in requested "
235 raise AttributeError("Problem decoding unicode in requested "
235 "method name.")
236 "method name.")
236
237
237 if isinstance(func, types.MethodType):
238 if isinstance(func, types.MethodType):
238 return func
239 return func
239 else:
240 else:
240 raise AttributeError("No such method: %s" % self._req_method)
241 raise AttributeError("No such method: %s" % self._req_method)
241
242
@@ -1,78 +1,78 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.branches
3 rhodecode.controllers.branches
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 branches controller for rhodecode
6 branches controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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 modify
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
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 from pylons import tmpl_context as c
28 from pylons import tmpl_context as c
29 import binascii
29 import binascii
30
30
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.lib.base import BaseRepoController, render
33 from rhodecode.lib.odict import OrderedDict
33 from rhodecode.lib.compat import OrderedDict
34 from rhodecode.lib import safe_unicode
34 from rhodecode.lib import safe_unicode
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class BranchesController(BaseRepoController):
38 class BranchesController(BaseRepoController):
39
39
40 @LoginRequired()
40 @LoginRequired()
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 'repository.admin')
42 'repository.admin')
43 def __before__(self):
43 def __before__(self):
44 super(BranchesController, self).__before__()
44 super(BranchesController, self).__before__()
45
45
46 def index(self):
46 def index(self):
47
47
48 def _branchtags(localrepo):
48 def _branchtags(localrepo):
49
49
50 bt = {}
50 bt = {}
51 bt_closed = {}
51 bt_closed = {}
52
52
53 for bn, heads in localrepo.branchmap().iteritems():
53 for bn, heads in localrepo.branchmap().iteritems():
54 tip = heads[-1]
54 tip = heads[-1]
55 if 'close' not in localrepo.changelog.read(tip)[5]:
55 if 'close' not in localrepo.changelog.read(tip)[5]:
56 bt[bn] = tip
56 bt[bn] = tip
57 else:
57 else:
58 bt_closed[bn] = tip
58 bt_closed[bn] = tip
59 return bt, bt_closed
59 return bt, bt_closed
60
60
61
61
62 bt, bt_closed = _branchtags(c.rhodecode_repo._repo)
62 bt, bt_closed = _branchtags(c.rhodecode_repo._repo)
63 cs_g = c.rhodecode_repo.get_changeset
63 cs_g = c.rhodecode_repo.get_changeset
64 _branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) for n, h in
64 _branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) for n, h in
65 bt.items()]
65 bt.items()]
66
66
67 _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) for n, h in
67 _closed_branches = [(safe_unicode(n), cs_g(binascii.hexlify(h)),) for n, h in
68 bt_closed.items()]
68 bt_closed.items()]
69
69
70 c.repo_branches = OrderedDict(sorted(_branches,
70 c.repo_branches = OrderedDict(sorted(_branches,
71 key=lambda ctx: ctx[0],
71 key=lambda ctx: ctx[0],
72 reverse=False))
72 reverse=False))
73 c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
73 c.repo_closed_branches = OrderedDict(sorted(_closed_branches,
74 key=lambda ctx: ctx[0],
74 key=lambda ctx: ctx[0],
75 reverse=False))
75 reverse=False))
76
76
77
77
78 return render('branches/branches.html')
78 return render('branches/branches.html')
@@ -1,121 +1,116 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changelog controller for rhodecode
6 changelog controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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 modify
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
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 try:
29 import json
30 except ImportError:
31 #python 2.5 compatibility
32 import simplejson as json
33
34 from mercurial import graphmod
28 from mercurial import graphmod
35 from pylons import request, session, tmpl_context as c
29 from pylons import request, session, tmpl_context as c
36
30
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.helpers import RepoPage
33 from rhodecode.lib.helpers import RepoPage
34 from rhodecode.lib.compat import json
40
35
41 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
42
37
43
38
44 class ChangelogController(BaseRepoController):
39 class ChangelogController(BaseRepoController):
45
40
46 @LoginRequired()
41 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 'repository.admin')
43 'repository.admin')
49 def __before__(self):
44 def __before__(self):
50 super(ChangelogController, self).__before__()
45 super(ChangelogController, self).__before__()
51 c.affected_files_cut_off = 60
46 c.affected_files_cut_off = 60
52
47
53 def index(self):
48 def index(self):
54 limit = 100
49 limit = 100
55 default = 20
50 default = 20
56 if request.params.get('size'):
51 if request.params.get('size'):
57 try:
52 try:
58 int_size = int(request.params.get('size'))
53 int_size = int(request.params.get('size'))
59 except ValueError:
54 except ValueError:
60 int_size = default
55 int_size = default
61 int_size = int_size if int_size <= limit else limit
56 int_size = int_size if int_size <= limit else limit
62 c.size = int_size
57 c.size = int_size
63 session['changelog_size'] = c.size
58 session['changelog_size'] = c.size
64 session.save()
59 session.save()
65 else:
60 else:
66 c.size = int(session.get('changelog_size', default))
61 c.size = int(session.get('changelog_size', default))
67
62
68 p = int(request.params.get('page', 1))
63 p = int(request.params.get('page', 1))
69 branch_name = request.params.get('branch', None)
64 branch_name = request.params.get('branch', None)
70 c.total_cs = len(c.rhodecode_repo)
65 c.total_cs = len(c.rhodecode_repo)
71 c.pagination = RepoPage(c.rhodecode_repo, page=p,
66 c.pagination = RepoPage(c.rhodecode_repo, page=p,
72 item_count=c.total_cs, items_per_page=c.size,
67 item_count=c.total_cs, items_per_page=c.size,
73 branch_name=branch_name)
68 branch_name=branch_name)
74
69
75 self._graph(c.rhodecode_repo, c.total_cs, c.size, p)
70 self._graph(c.rhodecode_repo, c.total_cs, c.size, p)
76
71
77 return render('changelog/changelog.html')
72 return render('changelog/changelog.html')
78
73
79 def changelog_details(self, cs):
74 def changelog_details(self, cs):
80 if request.environ.get('HTTP_X_PARTIAL_XHR'):
75 if request.environ.get('HTTP_X_PARTIAL_XHR'):
81 c.cs = c.rhodecode_repo.get_changeset(cs)
76 c.cs = c.rhodecode_repo.get_changeset(cs)
82 return render('changelog/changelog_details.html')
77 return render('changelog/changelog_details.html')
83
78
84 def _graph(self, repo, repo_size, size, p):
79 def _graph(self, repo, repo_size, size, p):
85 """
80 """
86 Generates a DAG graph for mercurial
81 Generates a DAG graph for mercurial
87
82
88 :param repo: repo instance
83 :param repo: repo instance
89 :param size: number of commits to show
84 :param size: number of commits to show
90 :param p: page number
85 :param p: page number
91 """
86 """
92 if not repo.revisions:
87 if not repo.revisions:
93 c.jsdata = json.dumps([])
88 c.jsdata = json.dumps([])
94 return
89 return
95
90
96 revcount = min(repo_size, size)
91 revcount = min(repo_size, size)
97 offset = 1 if p == 1 else ((p - 1) * revcount + 1)
92 offset = 1 if p == 1 else ((p - 1) * revcount + 1)
98 try:
93 try:
99 rev_end = repo.revisions.index(repo.revisions[(-1 * offset)])
94 rev_end = repo.revisions.index(repo.revisions[(-1 * offset)])
100 except IndexError:
95 except IndexError:
101 rev_end = repo.revisions.index(repo.revisions[-1])
96 rev_end = repo.revisions.index(repo.revisions[-1])
102 rev_start = max(0, rev_end - revcount)
97 rev_start = max(0, rev_end - revcount)
103
98
104 data = []
99 data = []
105 rev_end += 1
100 rev_end += 1
106
101
107 if repo.alias == 'git':
102 if repo.alias == 'git':
108 for _ in xrange(rev_start, rev_end):
103 for _ in xrange(rev_start, rev_end):
109 vtx = [0, 1]
104 vtx = [0, 1]
110 edges = [[0, 0, 1]]
105 edges = [[0, 0, 1]]
111 data.append(['', vtx, edges])
106 data.append(['', vtx, edges])
112
107
113 elif repo.alias == 'hg':
108 elif repo.alias == 'hg':
114 revs = list(reversed(xrange(rev_start, rev_end)))
109 revs = list(reversed(xrange(rev_start, rev_end)))
115 c.dag = graphmod.colored(graphmod.dagwalker(repo._repo, revs))
110 c.dag = graphmod.colored(graphmod.dagwalker(repo._repo, revs))
116 for (id, type, ctx, vtx, edges) in c.dag:
111 for (id, type, ctx, vtx, edges) in c.dag:
117 if type != graphmod.CHANGESET:
112 if type != graphmod.CHANGESET:
118 continue
113 continue
119 data.append(['', vtx, edges])
114 data.append(['', vtx, edges])
120
115
121 c.jsdata = json.dumps(data)
116 c.jsdata = json.dumps(data)
@@ -1,252 +1,252 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 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 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons import tmpl_context as c, url, request, response
29 from pylons import tmpl_context as c, url, request, response
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32
32
33 import rhodecode.lib.helpers as h
33 import rhodecode.lib.helpers as h
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.base import BaseRepoController, render
35 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.utils import EmptyChangeset
36 from rhodecode.lib.utils import EmptyChangeset
37 from rhodecode.lib.odict import OrderedDict
37 from rhodecode.lib.compat import OrderedDict
38
38
39 from vcs.exceptions import RepositoryError, ChangesetError, \
39 from vcs.exceptions import RepositoryError, ChangesetError, \
40 ChangesetDoesNotExistError
40 ChangesetDoesNotExistError
41 from vcs.nodes import FileNode
41 from vcs.nodes import FileNode
42 from vcs.utils import diffs as differ
42 from vcs.utils import diffs as differ
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class ChangesetController(BaseRepoController):
47 class ChangesetController(BaseRepoController):
48
48
49 @LoginRequired()
49 @LoginRequired()
50 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
50 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
51 'repository.admin')
51 'repository.admin')
52 def __before__(self):
52 def __before__(self):
53 super(ChangesetController, self).__before__()
53 super(ChangesetController, self).__before__()
54 c.affected_files_cut_off = 60
54 c.affected_files_cut_off = 60
55
55
56 def index(self, revision):
56 def index(self, revision):
57
57
58 def wrap_to_table(str):
58 def wrap_to_table(str):
59
59
60 return '''<table class="code-difftable">
60 return '''<table class="code-difftable">
61 <tr class="line">
61 <tr class="line">
62 <td class="lineno new"></td>
62 <td class="lineno new"></td>
63 <td class="code"><pre>%s</pre></td>
63 <td class="code"><pre>%s</pre></td>
64 </tr>
64 </tr>
65 </table>''' % str
65 </table>''' % str
66
66
67 #get ranges of revisions if preset
67 #get ranges of revisions if preset
68 rev_range = revision.split('...')[:2]
68 rev_range = revision.split('...')[:2]
69
69
70 try:
70 try:
71 if len(rev_range) == 2:
71 if len(rev_range) == 2:
72 rev_start = rev_range[0]
72 rev_start = rev_range[0]
73 rev_end = rev_range[1]
73 rev_end = rev_range[1]
74 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
74 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
75 end=rev_end)
75 end=rev_end)
76 else:
76 else:
77 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
77 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
78
78
79 c.cs_ranges = list(rev_ranges)
79 c.cs_ranges = list(rev_ranges)
80
80
81 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
81 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
82 log.error(traceback.format_exc())
82 log.error(traceback.format_exc())
83 h.flash(str(e), category='warning')
83 h.flash(str(e), category='warning')
84 return redirect(url('home'))
84 return redirect(url('home'))
85
85
86 c.changes = OrderedDict()
86 c.changes = OrderedDict()
87 c.sum_added = 0
87 c.sum_added = 0
88 c.sum_removed = 0
88 c.sum_removed = 0
89 c.lines_added = 0
89 c.lines_added = 0
90 c.lines_deleted = 0
90 c.lines_deleted = 0
91 c.cut_off = False # defines if cut off limit is reached
91 c.cut_off = False # defines if cut off limit is reached
92
92
93 # Iterate over ranges (default changeset view is always one changeset)
93 # Iterate over ranges (default changeset view is always one changeset)
94 for changeset in c.cs_ranges:
94 for changeset in c.cs_ranges:
95 c.changes[changeset.raw_id] = []
95 c.changes[changeset.raw_id] = []
96 try:
96 try:
97 changeset_parent = changeset.parents[0]
97 changeset_parent = changeset.parents[0]
98 except IndexError:
98 except IndexError:
99 changeset_parent = None
99 changeset_parent = None
100
100
101 #==================================================================
101 #==================================================================
102 # ADDED FILES
102 # ADDED FILES
103 #==================================================================
103 #==================================================================
104 for node in changeset.added:
104 for node in changeset.added:
105
105
106 filenode_old = FileNode(node.path, '', EmptyChangeset())
106 filenode_old = FileNode(node.path, '', EmptyChangeset())
107 if filenode_old.is_binary or node.is_binary:
107 if filenode_old.is_binary or node.is_binary:
108 diff = wrap_to_table(_('binary file'))
108 diff = wrap_to_table(_('binary file'))
109 st = (0, 0)
109 st = (0, 0)
110 else:
110 else:
111 # in this case node.size is good parameter since those are
111 # in this case node.size is good parameter since those are
112 # added nodes and their size defines how many changes were
112 # added nodes and their size defines how many changes were
113 # made
113 # made
114 c.sum_added += node.size
114 c.sum_added += node.size
115 if c.sum_added < self.cut_off_limit:
115 if c.sum_added < self.cut_off_limit:
116 f_gitdiff = differ.get_gitdiff(filenode_old, node)
116 f_gitdiff = differ.get_gitdiff(filenode_old, node)
117 d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
117 d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
118
118
119 st = d.stat()
119 st = d.stat()
120 diff = d.as_html()
120 diff = d.as_html()
121
121
122 else:
122 else:
123 diff = wrap_to_table(_('Changeset is to big and '
123 diff = wrap_to_table(_('Changeset is to big and '
124 'was cut off, see raw '
124 'was cut off, see raw '
125 'changeset instead'))
125 'changeset instead'))
126 c.cut_off = True
126 c.cut_off = True
127 break
127 break
128
128
129 cs1 = None
129 cs1 = None
130 cs2 = node.last_changeset.raw_id
130 cs2 = node.last_changeset.raw_id
131 c.lines_added += st[0]
131 c.lines_added += st[0]
132 c.lines_deleted += st[1]
132 c.lines_deleted += st[1]
133 c.changes[changeset.raw_id].append(('added', node, diff,
133 c.changes[changeset.raw_id].append(('added', node, diff,
134 cs1, cs2, st))
134 cs1, cs2, st))
135
135
136 #==================================================================
136 #==================================================================
137 # CHANGED FILES
137 # CHANGED FILES
138 #==================================================================
138 #==================================================================
139 if not c.cut_off:
139 if not c.cut_off:
140 for node in changeset.changed:
140 for node in changeset.changed:
141 try:
141 try:
142 filenode_old = changeset_parent.get_node(node.path)
142 filenode_old = changeset_parent.get_node(node.path)
143 except ChangesetError:
143 except ChangesetError:
144 log.warning('Unable to fetch parent node for diff')
144 log.warning('Unable to fetch parent node for diff')
145 filenode_old = FileNode(node.path, '',
145 filenode_old = FileNode(node.path, '',
146 EmptyChangeset())
146 EmptyChangeset())
147
147
148 if filenode_old.is_binary or node.is_binary:
148 if filenode_old.is_binary or node.is_binary:
149 diff = wrap_to_table(_('binary file'))
149 diff = wrap_to_table(_('binary file'))
150 st = (0, 0)
150 st = (0, 0)
151 else:
151 else:
152
152
153 if c.sum_removed < self.cut_off_limit:
153 if c.sum_removed < self.cut_off_limit:
154 f_gitdiff = differ.get_gitdiff(filenode_old, node)
154 f_gitdiff = differ.get_gitdiff(filenode_old, node)
155 d = differ.DiffProcessor(f_gitdiff,
155 d = differ.DiffProcessor(f_gitdiff,
156 format='gitdiff')
156 format='gitdiff')
157 st = d.stat()
157 st = d.stat()
158 if (st[0] + st[1]) * 256 > self.cut_off_limit:
158 if (st[0] + st[1]) * 256 > self.cut_off_limit:
159 diff = wrap_to_table(_('Diff is to big '
159 diff = wrap_to_table(_('Diff is to big '
160 'and was cut off, see '
160 'and was cut off, see '
161 'raw diff instead'))
161 'raw diff instead'))
162 else:
162 else:
163 diff = d.as_html()
163 diff = d.as_html()
164
164
165 if diff:
165 if diff:
166 c.sum_removed += len(diff)
166 c.sum_removed += len(diff)
167 else:
167 else:
168 diff = wrap_to_table(_('Changeset is to big and '
168 diff = wrap_to_table(_('Changeset is to big and '
169 'was cut off, see raw '
169 'was cut off, see raw '
170 'changeset instead'))
170 'changeset instead'))
171 c.cut_off = True
171 c.cut_off = True
172 break
172 break
173
173
174 cs1 = filenode_old.last_changeset.raw_id
174 cs1 = filenode_old.last_changeset.raw_id
175 cs2 = node.last_changeset.raw_id
175 cs2 = node.last_changeset.raw_id
176 c.lines_added += st[0]
176 c.lines_added += st[0]
177 c.lines_deleted += st[1]
177 c.lines_deleted += st[1]
178 c.changes[changeset.raw_id].append(('changed', node, diff,
178 c.changes[changeset.raw_id].append(('changed', node, diff,
179 cs1, cs2, st))
179 cs1, cs2, st))
180
180
181 #==================================================================
181 #==================================================================
182 # REMOVED FILES
182 # REMOVED FILES
183 #==================================================================
183 #==================================================================
184 if not c.cut_off:
184 if not c.cut_off:
185 for node in changeset.removed:
185 for node in changeset.removed:
186 c.changes[changeset.raw_id].append(('removed', node, None,
186 c.changes[changeset.raw_id].append(('removed', node, None,
187 None, None, (0, 0)))
187 None, None, (0, 0)))
188
188
189 if len(c.cs_ranges) == 1:
189 if len(c.cs_ranges) == 1:
190 c.changeset = c.cs_ranges[0]
190 c.changeset = c.cs_ranges[0]
191 c.changes = c.changes[c.changeset.raw_id]
191 c.changes = c.changes[c.changeset.raw_id]
192
192
193 return render('changeset/changeset.html')
193 return render('changeset/changeset.html')
194 else:
194 else:
195 return render('changeset/changeset_range.html')
195 return render('changeset/changeset_range.html')
196
196
197 def raw_changeset(self, revision):
197 def raw_changeset(self, revision):
198
198
199 method = request.GET.get('diff', 'show')
199 method = request.GET.get('diff', 'show')
200 try:
200 try:
201 c.scm_type = c.rhodecode_repo.alias
201 c.scm_type = c.rhodecode_repo.alias
202 c.changeset = c.rhodecode_repo.get_changeset(revision)
202 c.changeset = c.rhodecode_repo.get_changeset(revision)
203 except RepositoryError:
203 except RepositoryError:
204 log.error(traceback.format_exc())
204 log.error(traceback.format_exc())
205 return redirect(url('home'))
205 return redirect(url('home'))
206 else:
206 else:
207 try:
207 try:
208 c.changeset_parent = c.changeset.parents[0]
208 c.changeset_parent = c.changeset.parents[0]
209 except IndexError:
209 except IndexError:
210 c.changeset_parent = None
210 c.changeset_parent = None
211 c.changes = []
211 c.changes = []
212
212
213 for node in c.changeset.added:
213 for node in c.changeset.added:
214 filenode_old = FileNode(node.path, '')
214 filenode_old = FileNode(node.path, '')
215 if filenode_old.is_binary or node.is_binary:
215 if filenode_old.is_binary or node.is_binary:
216 diff = _('binary file') + '\n'
216 diff = _('binary file') + '\n'
217 else:
217 else:
218 f_gitdiff = differ.get_gitdiff(filenode_old, node)
218 f_gitdiff = differ.get_gitdiff(filenode_old, node)
219 diff = differ.DiffProcessor(f_gitdiff,
219 diff = differ.DiffProcessor(f_gitdiff,
220 format='gitdiff').raw_diff()
220 format='gitdiff').raw_diff()
221
221
222 cs1 = None
222 cs1 = None
223 cs2 = node.last_changeset.raw_id
223 cs2 = node.last_changeset.raw_id
224 c.changes.append(('added', node, diff, cs1, cs2))
224 c.changes.append(('added', node, diff, cs1, cs2))
225
225
226 for node in c.changeset.changed:
226 for node in c.changeset.changed:
227 filenode_old = c.changeset_parent.get_node(node.path)
227 filenode_old = c.changeset_parent.get_node(node.path)
228 if filenode_old.is_binary or node.is_binary:
228 if filenode_old.is_binary or node.is_binary:
229 diff = _('binary file')
229 diff = _('binary file')
230 else:
230 else:
231 f_gitdiff = differ.get_gitdiff(filenode_old, node)
231 f_gitdiff = differ.get_gitdiff(filenode_old, node)
232 diff = differ.DiffProcessor(f_gitdiff,
232 diff = differ.DiffProcessor(f_gitdiff,
233 format='gitdiff').raw_diff()
233 format='gitdiff').raw_diff()
234
234
235 cs1 = filenode_old.last_changeset.raw_id
235 cs1 = filenode_old.last_changeset.raw_id
236 cs2 = node.last_changeset.raw_id
236 cs2 = node.last_changeset.raw_id
237 c.changes.append(('changed', node, diff, cs1, cs2))
237 c.changes.append(('changed', node, diff, cs1, cs2))
238
238
239 response.content_type = 'text/plain'
239 response.content_type = 'text/plain'
240
240
241 if method == 'download':
241 if method == 'download':
242 response.content_disposition = 'attachment; filename=%s.patch' \
242 response.content_disposition = 'attachment; filename=%s.patch' \
243 % revision
243 % revision
244
244
245 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
245 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
246 c.changeset.parents])
246 c.changeset.parents])
247
247
248 c.diffs = ''
248 c.diffs = ''
249 for x in c.changes:
249 for x in c.changes:
250 c.diffs += x[2]
250 c.diffs += x[2]
251
251
252 return render('changeset/raw_changeset.html')
252 return render('changeset/raw_changeset.html')
@@ -1,507 +1,514 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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 modify
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
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from os.path import join as jn
30 from os.path import join as jn
31
31
32 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from pylons.decorators import jsonify
35 from pylons.decorators import jsonify
36
36
37 from vcs.conf import settings
37 from vcs.conf import settings
38 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
38 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
39 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
39 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
40 from vcs.nodes import FileNode, NodeKind
40 from vcs.nodes import FileNode, NodeKind
41 from vcs.utils import diffs as differ
41 from vcs.utils import diffs as differ
42
42
43 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
43 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
45 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.base import BaseRepoController, render
46 from rhodecode.lib.utils import EmptyChangeset
46 from rhodecode.lib.utils import EmptyChangeset
47 import rhodecode.lib.helpers as h
47 import rhodecode.lib.helpers as h
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class FilesController(BaseRepoController):
53 class FilesController(BaseRepoController):
54
54
55 @LoginRequired()
55 @LoginRequired()
56 def __before__(self):
56 def __before__(self):
57 super(FilesController, self).__before__()
57 super(FilesController, self).__before__()
58 c.cut_off_limit = self.cut_off_limit
58 c.cut_off_limit = self.cut_off_limit
59
59
60 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
60 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
61 """
61 """
62 Safe way to get changeset if error occur it redirects to tip with
62 Safe way to get changeset if error occur it redirects to tip with
63 proper message
63 proper message
64
64
65 :param rev: revision to fetch
65 :param rev: revision to fetch
66 :param repo_name: repo name to redirect after
66 :param repo_name: repo name to redirect after
67 """
67 """
68
68
69 try:
69 try:
70 return c.rhodecode_repo.get_changeset(rev)
70 return c.rhodecode_repo.get_changeset(rev)
71 except EmptyRepositoryError, e:
71 except EmptyRepositoryError, e:
72 if not redirect_after:
72 if not redirect_after:
73 return None
73 return None
74 url_ = url('files_add_home',
74 url_ = url('files_add_home',
75 repo_name=c.repo_name,
75 repo_name=c.repo_name,
76 revision=0, f_path='')
76 revision=0, f_path='')
77 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
77 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
78 h.flash(h.literal(_('There are no files yet %s' % add_new)),
78 h.flash(h.literal(_('There are no files yet %s' % add_new)),
79 category='warning')
79 category='warning')
80 redirect(h.url('summary_home', repo_name=repo_name))
80 redirect(h.url('summary_home', repo_name=repo_name))
81
81
82 except RepositoryError, e:
82 except RepositoryError, e:
83 h.flash(str(e), category='warning')
83 h.flash(str(e), category='warning')
84 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
84 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
85
85
86 def __get_filenode_or_redirect(self, repo_name, cs, path):
86 def __get_filenode_or_redirect(self, repo_name, cs, path):
87 """
87 """
88 Returns file_node, if error occurs or given path is directory,
88 Returns file_node, if error occurs or given path is directory,
89 it'll redirect to top level path
89 it'll redirect to top level path
90
90
91 :param repo_name: repo_name
91 :param repo_name: repo_name
92 :param cs: given changeset
92 :param cs: given changeset
93 :param path: path to lookup
93 :param path: path to lookup
94 """
94 """
95
95
96 try:
96 try:
97 file_node = cs.get_node(path)
97 file_node = cs.get_node(path)
98 if file_node.is_dir():
98 if file_node.is_dir():
99 raise RepositoryError('given path is a directory')
99 raise RepositoryError('given path is a directory')
100 except RepositoryError, e:
100 except RepositoryError, e:
101 h.flash(str(e), category='warning')
101 h.flash(str(e), category='warning')
102 redirect(h.url('files_home', repo_name=repo_name,
102 redirect(h.url('files_home', repo_name=repo_name,
103 revision=cs.raw_id))
103 revision=cs.raw_id))
104
104
105 return file_node
105 return file_node
106
106
107
107
108 def __get_paths(self, changeset, starting_path):
108 def __get_paths(self, changeset, starting_path):
109 """recursive walk in root dir and return a set of all path in that dir
109 """recursive walk in root dir and return a set of all path in that dir
110 based on repository walk function
110 based on repository walk function
111 """
111 """
112 _files = list()
112 _files = list()
113 _dirs = list()
113 _dirs = list()
114
114
115 try:
115 try:
116 tip = changeset
116 tip = changeset
117 for topnode, dirs, files in tip.walk(starting_path):
117 for topnode, dirs, files in tip.walk(starting_path):
118 for f in files:
118 for f in files:
119 _files.append(f.path)
119 _files.append(f.path)
120 for d in dirs:
120 for d in dirs:
121 _dirs.append(d.path)
121 _dirs.append(d.path)
122 except RepositoryError, e:
122 except RepositoryError, e:
123 log.debug(traceback.format_exc())
123 log.debug(traceback.format_exc())
124 pass
124 pass
125 return _dirs, _files
125 return _dirs, _files
126
126
127 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
127 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
128 'repository.admin')
128 'repository.admin')
129 def index(self, repo_name, revision, f_path):
129 def index(self, repo_name, revision, f_path):
130 #reditect to given revision from form if given
130 #reditect to given revision from form if given
131 post_revision = request.POST.get('at_rev', None)
131 post_revision = request.POST.get('at_rev', None)
132 if post_revision:
132 if post_revision:
133 cs = self.__get_cs_or_redirect(post_revision, repo_name)
133 cs = self.__get_cs_or_redirect(post_revision, repo_name)
134 redirect(url('files_home', repo_name=c.repo_name,
134 redirect(url('files_home', repo_name=c.repo_name,
135 revision=cs.raw_id, f_path=f_path))
135 revision=cs.raw_id, f_path=f_path))
136
136
137 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
137 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
138 c.branch = request.GET.get('branch', None)
138 c.branch = request.GET.get('branch', None)
139 c.f_path = f_path
139 c.f_path = f_path
140
140
141 cur_rev = c.changeset.revision
141 cur_rev = c.changeset.revision
142
142
143 #prev link
143 #prev link
144 try:
144 try:
145 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
145 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
146 c.url_prev = url('files_home', repo_name=c.repo_name,
146 c.url_prev = url('files_home', repo_name=c.repo_name,
147 revision=prev_rev.raw_id, f_path=f_path)
147 revision=prev_rev.raw_id, f_path=f_path)
148 if c.branch:
148 if c.branch:
149 c.url_prev += '?branch=%s' % c.branch
149 c.url_prev += '?branch=%s' % c.branch
150 except (ChangesetDoesNotExistError, VCSError):
150 except (ChangesetDoesNotExistError, VCSError):
151 c.url_prev = '#'
151 c.url_prev = '#'
152
152
153 #next link
153 #next link
154 try:
154 try:
155 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
155 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
156 c.url_next = url('files_home', repo_name=c.repo_name,
156 c.url_next = url('files_home', repo_name=c.repo_name,
157 revision=next_rev.raw_id, f_path=f_path)
157 revision=next_rev.raw_id, f_path=f_path)
158 if c.branch:
158 if c.branch:
159 c.url_next += '?branch=%s' % c.branch
159 c.url_next += '?branch=%s' % c.branch
160 except (ChangesetDoesNotExistError, VCSError):
160 except (ChangesetDoesNotExistError, VCSError):
161 c.url_next = '#'
161 c.url_next = '#'
162
162
163 #files or dirs
163 #files or dirs
164 try:
164 try:
165 c.files_list = c.changeset.get_node(f_path)
165 c.files_list = c.changeset.get_node(f_path)
166
166
167 if c.files_list.is_file():
167 if c.files_list.is_file():
168 c.file_history = self._get_node_history(c.changeset, f_path)
168 c.file_history = self._get_node_history(c.changeset, f_path)
169 else:
169 else:
170 c.file_history = []
170 c.file_history = []
171 except RepositoryError, e:
171 except RepositoryError, e:
172 h.flash(str(e), category='warning')
172 h.flash(str(e), category='warning')
173 redirect(h.url('files_home', repo_name=repo_name,
173 redirect(h.url('files_home', repo_name=repo_name,
174 revision=revision))
174 revision=revision))
175
175
176 return render('files/files.html')
176 return render('files/files.html')
177
177
178 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
178 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
179 'repository.admin')
179 'repository.admin')
180 def rawfile(self, repo_name, revision, f_path):
180 def rawfile(self, repo_name, revision, f_path):
181 cs = self.__get_cs_or_redirect(revision, repo_name)
181 cs = self.__get_cs_or_redirect(revision, repo_name)
182 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
182 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
183
183
184 response.content_disposition = 'attachment; filename=%s' % \
184 response.content_disposition = 'attachment; filename=%s' % \
185 safe_str(f_path.split(os.sep)[-1])
185 safe_str(f_path.split(os.sep)[-1])
186
186
187 response.content_type = file_node.mimetype
187 response.content_type = file_node.mimetype
188 return file_node.content
188 return file_node.content
189
189
190 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
190 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
191 'repository.admin')
191 'repository.admin')
192 def raw(self, repo_name, revision, f_path):
192 def raw(self, repo_name, revision, f_path):
193 cs = self.__get_cs_or_redirect(revision, repo_name)
193 cs = self.__get_cs_or_redirect(revision, repo_name)
194 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
194 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
195
195
196 raw_mimetype_mapping = {
196 raw_mimetype_mapping = {
197 # map original mimetype to a mimetype used for "show as raw"
197 # map original mimetype to a mimetype used for "show as raw"
198 # you can also provide a content-disposition to override the
198 # you can also provide a content-disposition to override the
199 # default "attachment" disposition.
199 # default "attachment" disposition.
200 # orig_type: (new_type, new_dispo)
200 # orig_type: (new_type, new_dispo)
201
201
202 # show images inline:
202 # show images inline:
203 'image/x-icon': ('image/x-icon', 'inline'),
203 'image/x-icon': ('image/x-icon', 'inline'),
204 'image/png': ('image/png', 'inline'),
204 'image/png': ('image/png', 'inline'),
205 'image/gif': ('image/gif', 'inline'),
205 'image/gif': ('image/gif', 'inline'),
206 'image/jpeg': ('image/jpeg', 'inline'),
206 'image/jpeg': ('image/jpeg', 'inline'),
207 'image/svg+xml': ('image/svg+xml', 'inline'),
207 'image/svg+xml': ('image/svg+xml', 'inline'),
208 }
208 }
209
209
210 mimetype = file_node.mimetype
210 mimetype = file_node.mimetype
211 try:
211 try:
212 mimetype, dispo = raw_mimetype_mapping[mimetype]
212 mimetype, dispo = raw_mimetype_mapping[mimetype]
213 except KeyError:
213 except KeyError:
214 # we don't know anything special about this, handle it safely
214 # we don't know anything special about this, handle it safely
215 if file_node.is_binary:
215 if file_node.is_binary:
216 # do same as download raw for binary files
216 # do same as download raw for binary files
217 mimetype, dispo = 'application/octet-stream', 'attachment'
217 mimetype, dispo = 'application/octet-stream', 'attachment'
218 else:
218 else:
219 # do not just use the original mimetype, but force text/plain,
219 # do not just use the original mimetype, but force text/plain,
220 # otherwise it would serve text/html and that might be unsafe.
220 # otherwise it would serve text/html and that might be unsafe.
221 # Note: underlying vcs library fakes text/plain mimetype if the
221 # Note: underlying vcs library fakes text/plain mimetype if the
222 # mimetype can not be determined and it thinks it is not
222 # mimetype can not be determined and it thinks it is not
223 # binary.This might lead to erroneous text display in some
223 # binary.This might lead to erroneous text display in some
224 # cases, but helps in other cases, like with text files
224 # cases, but helps in other cases, like with text files
225 # without extension.
225 # without extension.
226 mimetype, dispo = 'text/plain', 'inline'
226 mimetype, dispo = 'text/plain', 'inline'
227
227
228 if dispo == 'attachment':
228 if dispo == 'attachment':
229 dispo = 'attachment; filename=%s' % \
229 dispo = 'attachment; filename=%s' % \
230 safe_str(f_path.split(os.sep)[-1])
230 safe_str(f_path.split(os.sep)[-1])
231
231
232 response.content_disposition = dispo
232 response.content_disposition = dispo
233 response.content_type = mimetype
233 response.content_type = mimetype
234 return file_node.content
234 return file_node.content
235
235
236 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
236 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
237 'repository.admin')
237 'repository.admin')
238 def annotate(self, repo_name, revision, f_path):
238 def annotate(self, repo_name, revision, f_path):
239 c.cs = self.__get_cs_or_redirect(revision, repo_name)
239 c.cs = self.__get_cs_or_redirect(revision, repo_name)
240 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
240 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
241
241
242 c.file_history = self._get_node_history(c.cs, f_path)
242 c.file_history = self._get_node_history(c.cs, f_path)
243 c.f_path = f_path
243 c.f_path = f_path
244 return render('files/files_annotate.html')
244 return render('files/files_annotate.html')
245
245
246 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
246 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
247 def edit(self, repo_name, revision, f_path):
247 def edit(self, repo_name, revision, f_path):
248 r_post = request.POST
248 r_post = request.POST
249
249
250 c.cs = self.__get_cs_or_redirect(revision, repo_name)
250 c.cs = self.__get_cs_or_redirect(revision, repo_name)
251 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
251 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
252
252
253 if c.file.is_binary:
253 if c.file.is_binary:
254 return redirect(url('files_home', repo_name=c.repo_name,
254 return redirect(url('files_home', repo_name=c.repo_name,
255 revision=c.cs.raw_id, f_path=f_path))
255 revision=c.cs.raw_id, f_path=f_path))
256
256
257 c.f_path = f_path
257 c.f_path = f_path
258
258
259 if r_post:
259 if r_post:
260
260
261 old_content = c.file.content
261 old_content = c.file.content
262 sl = old_content.splitlines(1)
262 sl = old_content.splitlines(1)
263 first_line = sl[0] if sl else ''
263 first_line = sl[0] if sl else ''
264 # modes: 0 - Unix, 1 - Mac, 2 - DOS
264 # modes: 0 - Unix, 1 - Mac, 2 - DOS
265 mode = detect_mode(first_line, 0)
265 mode = detect_mode(first_line, 0)
266 content = convert_line_endings(r_post.get('content'), mode)
266 content = convert_line_endings(r_post.get('content'), mode)
267
267
268 message = r_post.get('message') or (_('Edited %s via RhodeCode')
268 message = r_post.get('message') or (_('Edited %s via RhodeCode')
269 % (f_path))
269 % (f_path))
270 author = self.rhodecode_user.full_contact
270 author = self.rhodecode_user.full_contact
271
271
272 if content == old_content:
272 if content == old_content:
273 h.flash(_('No changes'),
273 h.flash(_('No changes'),
274 category='warning')
274 category='warning')
275 return redirect(url('changeset_home', repo_name=c.repo_name,
275 return redirect(url('changeset_home', repo_name=c.repo_name,
276 revision='tip'))
276 revision='tip'))
277
277
278 try:
278 try:
279 self.scm_model.commit_change(repo=c.rhodecode_repo,
279 self.scm_model.commit_change(repo=c.rhodecode_repo,
280 repo_name=repo_name, cs=c.cs,
280 repo_name=repo_name, cs=c.cs,
281 user=self.rhodecode_user,
281 user=self.rhodecode_user,
282 author=author, message=message,
282 author=author, message=message,
283 content=content, f_path=f_path)
283 content=content, f_path=f_path)
284 h.flash(_('Successfully committed to %s' % f_path),
284 h.flash(_('Successfully committed to %s' % f_path),
285 category='success')
285 category='success')
286
286
287 except Exception:
287 except Exception:
288 log.error(traceback.format_exc())
288 log.error(traceback.format_exc())
289 h.flash(_('Error occurred during commit'), category='error')
289 h.flash(_('Error occurred during commit'), category='error')
290 return redirect(url('changeset_home',
290 return redirect(url('changeset_home',
291 repo_name=c.repo_name, revision='tip'))
291 repo_name=c.repo_name, revision='tip'))
292
292
293 return render('files/files_edit.html')
293 return render('files/files_edit.html')
294
294
295 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
295 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
296 def add(self, repo_name, revision, f_path):
296 def add(self, repo_name, revision, f_path):
297 r_post = request.POST
297 r_post = request.POST
298 c.cs = self.__get_cs_or_redirect(revision, repo_name,
298 c.cs = self.__get_cs_or_redirect(revision, repo_name,
299 redirect_after=False)
299 redirect_after=False)
300 if c.cs is None:
300 if c.cs is None:
301 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
301 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
302
302
303 c.f_path = f_path
303 c.f_path = f_path
304
304
305 if r_post:
305 if r_post:
306 unix_mode = 0
306 unix_mode = 0
307 content = convert_line_endings(r_post.get('content'), unix_mode)
307 content = convert_line_endings(r_post.get('content'), unix_mode)
308
308
309 message = r_post.get('message') or (_('Added %s via RhodeCode')
309 message = r_post.get('message') or (_('Added %s via RhodeCode')
310 % (f_path))
310 % (f_path))
311 location = r_post.get('location')
311 location = r_post.get('location')
312 filename = r_post.get('filename')
312 filename = r_post.get('filename')
313 file_obj = r_post.get('upload_file', None)
313 file_obj = r_post.get('upload_file', None)
314
314
315 if file_obj is not None and hasattr(file_obj, 'filename'):
315 if file_obj is not None and hasattr(file_obj, 'filename'):
316 filename = file_obj.filename
316 filename = file_obj.filename
317 content = file_obj.file
317 content = file_obj.file
318
318
319 #TODO: REMOVE THIS !!
320 ################################
321 import ipdb;ipdb.set_trace()
322 print 'setting ipdb debuggin for rhodecode.controllers.files.FilesController.add'
323 ################################
324
325
319 node_path = os.path.join(location, filename)
326 node_path = os.path.join(location, filename)
320 author = self.rhodecode_user.full_contact
327 author = self.rhodecode_user.full_contact
321
328
322 if not content:
329 if not content:
323 h.flash(_('No content'), category='warning')
330 h.flash(_('No content'), category='warning')
324 return redirect(url('changeset_home', repo_name=c.repo_name,
331 return redirect(url('changeset_home', repo_name=c.repo_name,
325 revision='tip'))
332 revision='tip'))
326 if not filename:
333 if not filename:
327 h.flash(_('No filename'), category='warning')
334 h.flash(_('No filename'), category='warning')
328 return redirect(url('changeset_home', repo_name=c.repo_name,
335 return redirect(url('changeset_home', repo_name=c.repo_name,
329 revision='tip'))
336 revision='tip'))
330
337
331 try:
338 try:
332 self.scm_model.create_node(repo=c.rhodecode_repo,
339 self.scm_model.create_node(repo=c.rhodecode_repo,
333 repo_name=repo_name, cs=c.cs,
340 repo_name=repo_name, cs=c.cs,
334 user=self.rhodecode_user,
341 user=self.rhodecode_user,
335 author=author, message=message,
342 author=author, message=message,
336 content=content, f_path=node_path)
343 content=content, f_path=node_path)
337 h.flash(_('Successfully committed to %s' % node_path),
344 h.flash(_('Successfully committed to %s' % node_path),
338 category='success')
345 category='success')
339 except NodeAlreadyExistsError, e:
346 except NodeAlreadyExistsError, e:
340 h.flash(_(e), category='error')
347 h.flash(_(e), category='error')
341 except Exception:
348 except Exception:
342 log.error(traceback.format_exc())
349 log.error(traceback.format_exc())
343 h.flash(_('Error occurred during commit'), category='error')
350 h.flash(_('Error occurred during commit'), category='error')
344 return redirect(url('changeset_home',
351 return redirect(url('changeset_home',
345 repo_name=c.repo_name, revision='tip'))
352 repo_name=c.repo_name, revision='tip'))
346
353
347 return render('files/files_add.html')
354 return render('files/files_add.html')
348
355
349 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
356 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
350 'repository.admin')
357 'repository.admin')
351 def archivefile(self, repo_name, fname):
358 def archivefile(self, repo_name, fname):
352
359
353 fileformat = None
360 fileformat = None
354 revision = None
361 revision = None
355 ext = None
362 ext = None
356 subrepos = request.GET.get('subrepos') == 'true'
363 subrepos = request.GET.get('subrepos') == 'true'
357
364
358 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
365 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
359 archive_spec = fname.split(ext_data[1])
366 archive_spec = fname.split(ext_data[1])
360 if len(archive_spec) == 2 and archive_spec[1] == '':
367 if len(archive_spec) == 2 and archive_spec[1] == '':
361 fileformat = a_type or ext_data[1]
368 fileformat = a_type or ext_data[1]
362 revision = archive_spec[0]
369 revision = archive_spec[0]
363 ext = ext_data[1]
370 ext = ext_data[1]
364
371
365 try:
372 try:
366 dbrepo = RepoModel().get_by_repo_name(repo_name)
373 dbrepo = RepoModel().get_by_repo_name(repo_name)
367 if dbrepo.enable_downloads is False:
374 if dbrepo.enable_downloads is False:
368 return _('downloads disabled')
375 return _('downloads disabled')
369
376
370 cs = c.rhodecode_repo.get_changeset(revision)
377 cs = c.rhodecode_repo.get_changeset(revision)
371 content_type = settings.ARCHIVE_SPECS[fileformat][0]
378 content_type = settings.ARCHIVE_SPECS[fileformat][0]
372 except ChangesetDoesNotExistError:
379 except ChangesetDoesNotExistError:
373 return _('Unknown revision %s') % revision
380 return _('Unknown revision %s') % revision
374 except EmptyRepositoryError:
381 except EmptyRepositoryError:
375 return _('Empty repository')
382 return _('Empty repository')
376 except (ImproperArchiveTypeError, KeyError):
383 except (ImproperArchiveTypeError, KeyError):
377 return _('Unknown archive type')
384 return _('Unknown archive type')
378
385
379 response.content_type = content_type
386 response.content_type = content_type
380 response.content_disposition = 'attachment; filename=%s-%s%s' \
387 response.content_disposition = 'attachment; filename=%s-%s%s' \
381 % (repo_name, revision, ext)
388 % (repo_name, revision, ext)
382
389
383 import tempfile
390 import tempfile
384 archive = tempfile.mkstemp()[1]
391 archive = tempfile.mkstemp()[1]
385 t = open(archive, 'wb')
392 t = open(archive, 'wb')
386 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
393 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
387
394
388 def get_chunked_archive(archive):
395 def get_chunked_archive(archive):
389 stream = open(archive, 'rb')
396 stream = open(archive, 'rb')
390 while True:
397 while True:
391 data = stream.read(4096)
398 data = stream.read(4096)
392 if not data:
399 if not data:
393 os.remove(archive)
400 os.remove(archive)
394 break
401 break
395 yield data
402 yield data
396
403
397 return get_chunked_archive(archive)
404 return get_chunked_archive(archive)
398
405
399 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
406 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
400 'repository.admin')
407 'repository.admin')
401 def diff(self, repo_name, f_path):
408 def diff(self, repo_name, f_path):
402 diff1 = request.GET.get('diff1')
409 diff1 = request.GET.get('diff1')
403 diff2 = request.GET.get('diff2')
410 diff2 = request.GET.get('diff2')
404 c.action = request.GET.get('diff')
411 c.action = request.GET.get('diff')
405 c.no_changes = diff1 == diff2
412 c.no_changes = diff1 == diff2
406 c.f_path = f_path
413 c.f_path = f_path
407 c.big_diff = False
414 c.big_diff = False
408
415
409 try:
416 try:
410 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
417 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
411 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
418 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
412 node1 = c.changeset_1.get_node(f_path)
419 node1 = c.changeset_1.get_node(f_path)
413 else:
420 else:
414 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
421 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
415 node1 = FileNode('.', '', changeset=c.changeset_1)
422 node1 = FileNode('.', '', changeset=c.changeset_1)
416
423
417 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
424 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
418 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
425 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
419 node2 = c.changeset_2.get_node(f_path)
426 node2 = c.changeset_2.get_node(f_path)
420 else:
427 else:
421 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
428 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
422 node2 = FileNode('.', '', changeset=c.changeset_2)
429 node2 = FileNode('.', '', changeset=c.changeset_2)
423 except RepositoryError:
430 except RepositoryError:
424 return redirect(url('files_home',
431 return redirect(url('files_home',
425 repo_name=c.repo_name, f_path=f_path))
432 repo_name=c.repo_name, f_path=f_path))
426
433
427 if c.action == 'download':
434 if c.action == 'download':
428 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
435 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
429 format='gitdiff')
436 format='gitdiff')
430
437
431 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
438 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
432 response.content_type = 'text/plain'
439 response.content_type = 'text/plain'
433 response.content_disposition = 'attachment; filename=%s' \
440 response.content_disposition = 'attachment; filename=%s' \
434 % diff_name
441 % diff_name
435 return diff.raw_diff()
442 return diff.raw_diff()
436
443
437 elif c.action == 'raw':
444 elif c.action == 'raw':
438 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
445 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
439 format='gitdiff')
446 format='gitdiff')
440 response.content_type = 'text/plain'
447 response.content_type = 'text/plain'
441 return diff.raw_diff()
448 return diff.raw_diff()
442
449
443 elif c.action == 'diff':
450 elif c.action == 'diff':
444 if node1.is_binary or node2.is_binary:
451 if node1.is_binary or node2.is_binary:
445 c.cur_diff = _('Binary file')
452 c.cur_diff = _('Binary file')
446 elif node1.size > self.cut_off_limit or \
453 elif node1.size > self.cut_off_limit or \
447 node2.size > self.cut_off_limit:
454 node2.size > self.cut_off_limit:
448 c.cur_diff = ''
455 c.cur_diff = ''
449 c.big_diff = True
456 c.big_diff = True
450 else:
457 else:
451 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
458 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
452 format='gitdiff')
459 format='gitdiff')
453 c.cur_diff = diff.as_html()
460 c.cur_diff = diff.as_html()
454 else:
461 else:
455
462
456 #default option
463 #default option
457 if node1.is_binary or node2.is_binary:
464 if node1.is_binary or node2.is_binary:
458 c.cur_diff = _('Binary file')
465 c.cur_diff = _('Binary file')
459 elif node1.size > self.cut_off_limit or \
466 elif node1.size > self.cut_off_limit or \
460 node2.size > self.cut_off_limit:
467 node2.size > self.cut_off_limit:
461 c.cur_diff = ''
468 c.cur_diff = ''
462 c.big_diff = True
469 c.big_diff = True
463
470
464 else:
471 else:
465 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
472 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
466 format='gitdiff')
473 format='gitdiff')
467 c.cur_diff = diff.as_html()
474 c.cur_diff = diff.as_html()
468
475
469 if not c.cur_diff and not c.big_diff:
476 if not c.cur_diff and not c.big_diff:
470 c.no_changes = True
477 c.no_changes = True
471 return render('files/file_diff.html')
478 return render('files/file_diff.html')
472
479
473 def _get_node_history(self, cs, f_path):
480 def _get_node_history(self, cs, f_path):
474 changesets = cs.get_file_history(f_path)
481 changesets = cs.get_file_history(f_path)
475 hist_l = []
482 hist_l = []
476
483
477 changesets_group = ([], _("Changesets"))
484 changesets_group = ([], _("Changesets"))
478 branches_group = ([], _("Branches"))
485 branches_group = ([], _("Branches"))
479 tags_group = ([], _("Tags"))
486 tags_group = ([], _("Tags"))
480
487
481 for chs in changesets:
488 for chs in changesets:
482 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
489 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
483 changesets_group[0].append((chs.raw_id, n_desc,))
490 changesets_group[0].append((chs.raw_id, n_desc,))
484
491
485 hist_l.append(changesets_group)
492 hist_l.append(changesets_group)
486
493
487 for name, chs in c.rhodecode_repo.branches.items():
494 for name, chs in c.rhodecode_repo.branches.items():
488 #chs = chs.split(':')[-1]
495 #chs = chs.split(':')[-1]
489 branches_group[0].append((chs, name),)
496 branches_group[0].append((chs, name),)
490 hist_l.append(branches_group)
497 hist_l.append(branches_group)
491
498
492 for name, chs in c.rhodecode_repo.tags.items():
499 for name, chs in c.rhodecode_repo.tags.items():
493 #chs = chs.split(':')[-1]
500 #chs = chs.split(':')[-1]
494 tags_group[0].append((chs, name),)
501 tags_group[0].append((chs, name),)
495 hist_l.append(tags_group)
502 hist_l.append(tags_group)
496
503
497 return hist_l
504 return hist_l
498
505
499 @jsonify
506 @jsonify
500 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
507 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
501 'repository.admin')
508 'repository.admin')
502 def nodelist(self, repo_name, revision, f_path):
509 def nodelist(self, repo_name, revision, f_path):
503 if request.environ.get('HTTP_X_PARTIAL_XHR'):
510 if request.environ.get('HTTP_X_PARTIAL_XHR'):
504 cs = self.__get_cs_or_redirect(revision, repo_name)
511 cs = self.__get_cs_or_redirect(revision, repo_name)
505 _d, _f = self.__get_paths(cs, f_path)
512 _d, _f = self.__get_paths(cs, f_path)
506 return _d + _f
513 return _d + _f
507
514
@@ -1,189 +1,183 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.summary
3 rhodecode.controllers.summary
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Summary controller for Rhodecode
6 Summary controller for Rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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 modify
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
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import calendar
26 import calendar
27 import logging
27 import logging
28 from time import mktime
28 from time import mktime
29 from datetime import datetime, timedelta, date
29 from datetime import datetime, timedelta, date
30
30
31 from vcs.exceptions import ChangesetError
31 from vcs.exceptions import ChangesetError
32
32
33 from pylons import tmpl_context as c, request, url
33 from pylons import tmpl_context as c, request, url
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from rhodecode.model.db import Statistics, Repository
36 from rhodecode.model.db import Statistics, Repository
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38
38
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.utils import EmptyChangeset
41 from rhodecode.lib.utils import EmptyChangeset
42 from rhodecode.lib.odict import OrderedDict
43
42
44 from rhodecode.lib.celerylib import run_task
43 from rhodecode.lib.celerylib import run_task
45 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
44 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
46 LANGUAGES_EXTENSIONS_MAP
45 LANGUAGES_EXTENSIONS_MAP
47 from rhodecode.lib.helpers import RepoPage
46 from rhodecode.lib.helpers import RepoPage
47 from rhodecode.lib.compat import json, OrderedDict
48
48
49 try:
50 import json
51 except ImportError:
52 #python 2.5 compatibility
53 import simplejson as json
54
55 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
56
50
57
51
58 class SummaryController(BaseRepoController):
52 class SummaryController(BaseRepoController):
59
53
60 @LoginRequired()
54 @LoginRequired()
61 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
55 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
62 'repository.admin')
56 'repository.admin')
63 def __before__(self):
57 def __before__(self):
64 super(SummaryController, self).__before__()
58 super(SummaryController, self).__before__()
65
59
66 def index(self, repo_name):
60 def index(self, repo_name):
67
61
68 e = request.environ
62 e = request.environ
69 c.dbrepo = dbrepo = c.rhodecode_db_repo
63 c.dbrepo = dbrepo = c.rhodecode_db_repo
70
64
71 c.following = self.scm_model.is_following_repo(repo_name,
65 c.following = self.scm_model.is_following_repo(repo_name,
72 self.rhodecode_user.user_id)
66 self.rhodecode_user.user_id)
73
67
74 def url_generator(**kw):
68 def url_generator(**kw):
75 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
69 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
76
70
77 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
71 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
78 items_per_page=10, url=url_generator)
72 items_per_page=10, url=url_generator)
79
73
80 if self.rhodecode_user.username == 'default':
74 if self.rhodecode_user.username == 'default':
81 #for default(anonymous) user we don't need to pass credentials
75 #for default(anonymous) user we don't need to pass credentials
82 username = ''
76 username = ''
83 password = ''
77 password = ''
84 else:
78 else:
85 username = str(self.rhodecode_user.username)
79 username = str(self.rhodecode_user.username)
86 password = '@'
80 password = '@'
87
81
88 if e.get('wsgi.url_scheme') == 'https':
82 if e.get('wsgi.url_scheme') == 'https':
89 split_s = 'https://'
83 split_s = 'https://'
90 else:
84 else:
91 split_s = 'http://'
85 split_s = 'http://'
92
86
93 qualified_uri = [split_s] + [url.current(qualified=True)\
87 qualified_uri = [split_s] + [url.current(qualified=True)\
94 .split(split_s)[-1]]
88 .split(split_s)[-1]]
95 uri = u'%(proto)s%(user)s%(pass)s%(rest)s' \
89 uri = u'%(proto)s%(user)s%(pass)s%(rest)s' \
96 % {'user': username,
90 % {'user': username,
97 'pass': password,
91 'pass': password,
98 'proto': qualified_uri[0],
92 'proto': qualified_uri[0],
99 'rest': qualified_uri[1]}
93 'rest': qualified_uri[1]}
100 c.clone_repo_url = uri
94 c.clone_repo_url = uri
101 c.repo_tags = OrderedDict()
95 c.repo_tags = OrderedDict()
102 for name, hash in c.rhodecode_repo.tags.items()[:10]:
96 for name, hash in c.rhodecode_repo.tags.items()[:10]:
103 try:
97 try:
104 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
98 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
105 except ChangesetError:
99 except ChangesetError:
106 c.repo_tags[name] = EmptyChangeset(hash)
100 c.repo_tags[name] = EmptyChangeset(hash)
107
101
108 c.repo_branches = OrderedDict()
102 c.repo_branches = OrderedDict()
109 for name, hash in c.rhodecode_repo.branches.items()[:10]:
103 for name, hash in c.rhodecode_repo.branches.items()[:10]:
110 try:
104 try:
111 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
105 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
112 except ChangesetError:
106 except ChangesetError:
113 c.repo_branches[name] = EmptyChangeset(hash)
107 c.repo_branches[name] = EmptyChangeset(hash)
114
108
115 td = date.today() + timedelta(days=1)
109 td = date.today() + timedelta(days=1)
116 td_1m = td - timedelta(days=calendar.mdays[td.month])
110 td_1m = td - timedelta(days=calendar.mdays[td.month])
117 td_1y = td - timedelta(days=365)
111 td_1y = td - timedelta(days=365)
118
112
119 ts_min_m = mktime(td_1m.timetuple())
113 ts_min_m = mktime(td_1m.timetuple())
120 ts_min_y = mktime(td_1y.timetuple())
114 ts_min_y = mktime(td_1y.timetuple())
121 ts_max_y = mktime(td.timetuple())
115 ts_max_y = mktime(td.timetuple())
122
116
123 if dbrepo.enable_statistics:
117 if dbrepo.enable_statistics:
124 c.no_data_msg = _('No data loaded yet')
118 c.no_data_msg = _('No data loaded yet')
125 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
119 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
126 else:
120 else:
127 c.no_data_msg = _('Statistics are disabled for this repository')
121 c.no_data_msg = _('Statistics are disabled for this repository')
128 c.ts_min = ts_min_m
122 c.ts_min = ts_min_m
129 c.ts_max = ts_max_y
123 c.ts_max = ts_max_y
130
124
131 stats = self.sa.query(Statistics)\
125 stats = self.sa.query(Statistics)\
132 .filter(Statistics.repository == dbrepo)\
126 .filter(Statistics.repository == dbrepo)\
133 .scalar()
127 .scalar()
134
128
135 c.stats_percentage = 0
129 c.stats_percentage = 0
136
130
137 if stats and stats.languages:
131 if stats and stats.languages:
138 c.no_data = False is dbrepo.enable_statistics
132 c.no_data = False is dbrepo.enable_statistics
139 lang_stats_d = json.loads(stats.languages)
133 lang_stats_d = json.loads(stats.languages)
140 c.commit_data = stats.commit_activity
134 c.commit_data = stats.commit_activity
141 c.overview_data = stats.commit_activity_combined
135 c.overview_data = stats.commit_activity_combined
142
136
143 lang_stats = ((x, {"count": y,
137 lang_stats = ((x, {"count": y,
144 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
138 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
145 for x, y in lang_stats_d.items())
139 for x, y in lang_stats_d.items())
146
140
147 c.trending_languages = json.dumps(OrderedDict(
141 c.trending_languages = json.dumps(OrderedDict(
148 sorted(lang_stats, reverse=True,
142 sorted(lang_stats, reverse=True,
149 key=lambda k: k[1])[:10]
143 key=lambda k: k[1])[:10]
150 )
144 )
151 )
145 )
152 last_rev = stats.stat_on_revision
146 last_rev = stats.stat_on_revision
153 c.repo_last_rev = c.rhodecode_repo.count() - 1 \
147 c.repo_last_rev = c.rhodecode_repo.count() - 1 \
154 if c.rhodecode_repo.revisions else 0
148 if c.rhodecode_repo.revisions else 0
155 if last_rev == 0 or c.repo_last_rev == 0:
149 if last_rev == 0 or c.repo_last_rev == 0:
156 pass
150 pass
157 else:
151 else:
158 c.stats_percentage = '%.2f' % ((float((last_rev)) /
152 c.stats_percentage = '%.2f' % ((float((last_rev)) /
159 c.repo_last_rev) * 100)
153 c.repo_last_rev) * 100)
160 else:
154 else:
161 c.commit_data = json.dumps({})
155 c.commit_data = json.dumps({})
162 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
156 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
163 c.trending_languages = json.dumps({})
157 c.trending_languages = json.dumps({})
164 c.no_data = True
158 c.no_data = True
165
159
166 c.enable_downloads = dbrepo.enable_downloads
160 c.enable_downloads = dbrepo.enable_downloads
167 if c.enable_downloads:
161 if c.enable_downloads:
168 c.download_options = self._get_download_links(c.rhodecode_repo)
162 c.download_options = self._get_download_links(c.rhodecode_repo)
169
163
170 return render('summary/summary.html')
164 return render('summary/summary.html')
171
165
172 def _get_download_links(self, repo):
166 def _get_download_links(self, repo):
173
167
174 download_l = []
168 download_l = []
175
169
176 branches_group = ([], _("Branches"))
170 branches_group = ([], _("Branches"))
177 tags_group = ([], _("Tags"))
171 tags_group = ([], _("Tags"))
178
172
179 for name, chs in c.rhodecode_repo.branches.items():
173 for name, chs in c.rhodecode_repo.branches.items():
180 #chs = chs.split(':')[-1]
174 #chs = chs.split(':')[-1]
181 branches_group[0].append((chs, name),)
175 branches_group[0].append((chs, name),)
182 download_l.append(branches_group)
176 download_l.append(branches_group)
183
177
184 for name, chs in c.rhodecode_repo.tags.items():
178 for name, chs in c.rhodecode_repo.tags.items():
185 #chs = chs.split(':')[-1]
179 #chs = chs.split(':')[-1]
186 tags_group[0].append((chs, name),)
180 tags_group[0].append((chs, name),)
187 download_l.append(tags_group)
181 download_l.append(tags_group)
188
182
189 return download_l
183 return download_l
@@ -1,53 +1,53 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.tags
3 rhodecode.controllers.tags
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Tags controller for rhodecode
6 Tags controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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 modify
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
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26
26
27 from pylons import tmpl_context as c
27 from pylons import tmpl_context as c
28
28
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.base import BaseRepoController, render
30 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.odict import OrderedDict
31 from rhodecode.lib.compat import OrderedDict
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class TagsController(BaseRepoController):
36 class TagsController(BaseRepoController):
37
37
38 @LoginRequired()
38 @LoginRequired()
39 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
39 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
40 'repository.admin')
40 'repository.admin')
41 def __before__(self):
41 def __before__(self):
42 super(TagsController, self).__before__()
42 super(TagsController, self).__before__()
43
43
44 def index(self):
44 def index(self):
45 c.repo_tags = OrderedDict()
45 c.repo_tags = OrderedDict()
46
46
47 tags = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
47 tags = [(name, c.rhodecode_repo.get_changeset(hash_)) for \
48 name, hash_ in c.rhodecode_repo.tags.items()]
48 name, hash_ in c.rhodecode_repo.tags.items()]
49 ordered_tags = sorted(tags, key=lambda x: x[1].date, reverse=True)
49 ordered_tags = sorted(tags, key=lambda x: x[1].date, reverse=True)
50 for name, cs_tag in ordered_tags:
50 for name, cs_tag in ordered_tags:
51 c.repo_tags[name] = cs_tag
51 c.repo_tags[name] = cs_tag
52
52
53 return render('tags/tags.html')
53 return render('tags/tags.html')
@@ -1,389 +1,381 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.__init__
3 rhodecode.lib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 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 modify
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
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26
27 try:
28 import json
29 except ImportError:
30 #python 2.5 compatibility
31 import simplejson as json
32
33
34 def __get_lem():
26 def __get_lem():
35 from pygments import lexers
27 from pygments import lexers
36 from string import lower
28 from string import lower
37 from collections import defaultdict
29 from collections import defaultdict
38
30
39 d = defaultdict(lambda: [])
31 d = defaultdict(lambda: [])
40
32
41 def __clean(s):
33 def __clean(s):
42 s = s.lstrip('*')
34 s = s.lstrip('*')
43 s = s.lstrip('.')
35 s = s.lstrip('.')
44
36
45 if s.find('[') != -1:
37 if s.find('[') != -1:
46 exts = []
38 exts = []
47 start, stop = s.find('['), s.find(']')
39 start, stop = s.find('['), s.find(']')
48
40
49 for suffix in s[start + 1:stop]:
41 for suffix in s[start + 1:stop]:
50 exts.append(s[:s.find('[')] + suffix)
42 exts.append(s[:s.find('[')] + suffix)
51 return map(lower, exts)
43 return map(lower, exts)
52 else:
44 else:
53 return map(lower, [s])
45 return map(lower, [s])
54
46
55 for lx, t in sorted(lexers.LEXERS.items()):
47 for lx, t in sorted(lexers.LEXERS.items()):
56 m = map(__clean, t[-2])
48 m = map(__clean, t[-2])
57 if m:
49 if m:
58 m = reduce(lambda x, y: x + y, m)
50 m = reduce(lambda x, y: x + y, m)
59 for ext in m:
51 for ext in m:
60 desc = lx.replace('Lexer', '')
52 desc = lx.replace('Lexer', '')
61 d[ext].append(desc)
53 d[ext].append(desc)
62
54
63 return dict(d)
55 return dict(d)
64
56
65 # language map is also used by whoosh indexer, which for those specified
57 # language map is also used by whoosh indexer, which for those specified
66 # extensions will index it's content
58 # extensions will index it's content
67 LANGUAGES_EXTENSIONS_MAP = __get_lem()
59 LANGUAGES_EXTENSIONS_MAP = __get_lem()
68
60
69 # Additional mappings that are not present in the pygments lexers
61 # Additional mappings that are not present in the pygments lexers
70 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
62 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
71 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
63 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
72
64
73 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
65 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
74
66
75
67
76 def str2bool(_str):
68 def str2bool(_str):
77 """
69 """
78 returs True/False value from given string, it tries to translate the
70 returs True/False value from given string, it tries to translate the
79 string into boolean
71 string into boolean
80
72
81 :param _str: string value to translate into boolean
73 :param _str: string value to translate into boolean
82 :rtype: boolean
74 :rtype: boolean
83 :returns: boolean from given string
75 :returns: boolean from given string
84 """
76 """
85 if _str is None:
77 if _str is None:
86 return False
78 return False
87 if _str in (True, False):
79 if _str in (True, False):
88 return _str
80 return _str
89 _str = str(_str).strip().lower()
81 _str = str(_str).strip().lower()
90 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
82 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
91
83
92
84
93 def convert_line_endings(line, mode):
85 def convert_line_endings(line, mode):
94 """
86 """
95 Converts a given line "line end" accordingly to given mode
87 Converts a given line "line end" accordingly to given mode
96
88
97 Available modes are::
89 Available modes are::
98 0 - Unix
90 0 - Unix
99 1 - Mac
91 1 - Mac
100 2 - DOS
92 2 - DOS
101
93
102 :param line: given line to convert
94 :param line: given line to convert
103 :param mode: mode to convert to
95 :param mode: mode to convert to
104 :rtype: str
96 :rtype: str
105 :return: converted line according to mode
97 :return: converted line according to mode
106 """
98 """
107 from string import replace
99 from string import replace
108
100
109 if mode == 0:
101 if mode == 0:
110 line = replace(line, '\r\n', '\n')
102 line = replace(line, '\r\n', '\n')
111 line = replace(line, '\r', '\n')
103 line = replace(line, '\r', '\n')
112 elif mode == 1:
104 elif mode == 1:
113 line = replace(line, '\r\n', '\r')
105 line = replace(line, '\r\n', '\r')
114 line = replace(line, '\n', '\r')
106 line = replace(line, '\n', '\r')
115 elif mode == 2:
107 elif mode == 2:
116 import re
108 import re
117 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
109 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
118 return line
110 return line
119
111
120
112
121 def detect_mode(line, default):
113 def detect_mode(line, default):
122 """
114 """
123 Detects line break for given line, if line break couldn't be found
115 Detects line break for given line, if line break couldn't be found
124 given default value is returned
116 given default value is returned
125
117
126 :param line: str line
118 :param line: str line
127 :param default: default
119 :param default: default
128 :rtype: int
120 :rtype: int
129 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
121 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
130 """
122 """
131 if line.endswith('\r\n'):
123 if line.endswith('\r\n'):
132 return 2
124 return 2
133 elif line.endswith('\n'):
125 elif line.endswith('\n'):
134 return 0
126 return 0
135 elif line.endswith('\r'):
127 elif line.endswith('\r'):
136 return 1
128 return 1
137 else:
129 else:
138 return default
130 return default
139
131
140
132
141 def generate_api_key(username, salt=None):
133 def generate_api_key(username, salt=None):
142 """
134 """
143 Generates unique API key for given username, if salt is not given
135 Generates unique API key for given username, if salt is not given
144 it'll be generated from some random string
136 it'll be generated from some random string
145
137
146 :param username: username as string
138 :param username: username as string
147 :param salt: salt to hash generate KEY
139 :param salt: salt to hash generate KEY
148 :rtype: str
140 :rtype: str
149 :returns: sha1 hash from username+salt
141 :returns: sha1 hash from username+salt
150 """
142 """
151 from tempfile import _RandomNameSequence
143 from tempfile import _RandomNameSequence
152 import hashlib
144 import hashlib
153
145
154 if salt is None:
146 if salt is None:
155 salt = _RandomNameSequence().next()
147 salt = _RandomNameSequence().next()
156
148
157 return hashlib.sha1(username + salt).hexdigest()
149 return hashlib.sha1(username + salt).hexdigest()
158
150
159
151
160 def safe_unicode(str_, from_encoding='utf8'):
152 def safe_unicode(str_, from_encoding='utf8'):
161 """
153 """
162 safe unicode function. Does few trick to turn str_ into unicode
154 safe unicode function. Does few trick to turn str_ into unicode
163
155
164 In case of UnicodeDecode error we try to return it with encoding detected
156 In case of UnicodeDecode error we try to return it with encoding detected
165 by chardet library if it fails fallback to unicode with errors replaced
157 by chardet library if it fails fallback to unicode with errors replaced
166
158
167 :param str_: string to decode
159 :param str_: string to decode
168 :rtype: unicode
160 :rtype: unicode
169 :returns: unicode object
161 :returns: unicode object
170 """
162 """
171 if isinstance(str_, unicode):
163 if isinstance(str_, unicode):
172 return str_
164 return str_
173
165
174 try:
166 try:
175 return unicode(str_)
167 return unicode(str_)
176 except UnicodeDecodeError:
168 except UnicodeDecodeError:
177 pass
169 pass
178
170
179 try:
171 try:
180 return unicode(str_, from_encoding)
172 return unicode(str_, from_encoding)
181 except UnicodeDecodeError:
173 except UnicodeDecodeError:
182 pass
174 pass
183
175
184 try:
176 try:
185 import chardet
177 import chardet
186 encoding = chardet.detect(str_)['encoding']
178 encoding = chardet.detect(str_)['encoding']
187 if encoding is None:
179 if encoding is None:
188 raise Exception()
180 raise Exception()
189 return str_.decode(encoding)
181 return str_.decode(encoding)
190 except (ImportError, UnicodeDecodeError, Exception):
182 except (ImportError, UnicodeDecodeError, Exception):
191 return unicode(str_, from_encoding, 'replace')
183 return unicode(str_, from_encoding, 'replace')
192
184
193 def safe_str(unicode_, to_encoding='utf8'):
185 def safe_str(unicode_, to_encoding='utf8'):
194 """
186 """
195 safe str function. Does few trick to turn unicode_ into string
187 safe str function. Does few trick to turn unicode_ into string
196
188
197 In case of UnicodeEncodeError we try to return it with encoding detected
189 In case of UnicodeEncodeError we try to return it with encoding detected
198 by chardet library if it fails fallback to string with errors replaced
190 by chardet library if it fails fallback to string with errors replaced
199
191
200 :param unicode_: unicode to encode
192 :param unicode_: unicode to encode
201 :rtype: str
193 :rtype: str
202 :returns: str object
194 :returns: str object
203 """
195 """
204
196
205 if isinstance(unicode_, str):
197 if isinstance(unicode_, str):
206 return unicode_
198 return unicode_
207
199
208 try:
200 try:
209 return unicode_.encode(to_encoding)
201 return unicode_.encode(to_encoding)
210 except UnicodeEncodeError:
202 except UnicodeEncodeError:
211 pass
203 pass
212
204
213 try:
205 try:
214 import chardet
206 import chardet
215 encoding = chardet.detect(unicode_)['encoding']
207 encoding = chardet.detect(unicode_)['encoding']
216 print encoding
208 print encoding
217 if encoding is None:
209 if encoding is None:
218 raise UnicodeEncodeError()
210 raise UnicodeEncodeError()
219
211
220 return unicode_.encode(encoding)
212 return unicode_.encode(encoding)
221 except (ImportError, UnicodeEncodeError):
213 except (ImportError, UnicodeEncodeError):
222 return unicode_.encode(to_encoding, 'replace')
214 return unicode_.encode(to_encoding, 'replace')
223
215
224 return safe_str
216 return safe_str
225
217
226
218
227
219
228 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
220 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
229 """
221 """
230 Custom engine_from_config functions that makes sure we use NullPool for
222 Custom engine_from_config functions that makes sure we use NullPool for
231 file based sqlite databases. This prevents errors on sqlite. This only
223 file based sqlite databases. This prevents errors on sqlite. This only
232 applies to sqlalchemy versions < 0.7.0
224 applies to sqlalchemy versions < 0.7.0
233
225
234 """
226 """
235 import sqlalchemy
227 import sqlalchemy
236 from sqlalchemy import engine_from_config as efc
228 from sqlalchemy import engine_from_config as efc
237 import logging
229 import logging
238
230
239 if int(sqlalchemy.__version__.split('.')[1]) < 7:
231 if int(sqlalchemy.__version__.split('.')[1]) < 7:
240
232
241 # This solution should work for sqlalchemy < 0.7.0, and should use
233 # This solution should work for sqlalchemy < 0.7.0, and should use
242 # proxy=TimerProxy() for execution time profiling
234 # proxy=TimerProxy() for execution time profiling
243
235
244 from sqlalchemy.pool import NullPool
236 from sqlalchemy.pool import NullPool
245 url = configuration[prefix + 'url']
237 url = configuration[prefix + 'url']
246
238
247 if url.startswith('sqlite'):
239 if url.startswith('sqlite'):
248 kwargs.update({'poolclass': NullPool})
240 kwargs.update({'poolclass': NullPool})
249 return efc(configuration, prefix, **kwargs)
241 return efc(configuration, prefix, **kwargs)
250 else:
242 else:
251 import time
243 import time
252 from sqlalchemy import event
244 from sqlalchemy import event
253 from sqlalchemy.engine import Engine
245 from sqlalchemy.engine import Engine
254
246
255 log = logging.getLogger('sqlalchemy.engine')
247 log = logging.getLogger('sqlalchemy.engine')
256 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
248 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
257 engine = efc(configuration, prefix, **kwargs)
249 engine = efc(configuration, prefix, **kwargs)
258
250
259 def color_sql(sql):
251 def color_sql(sql):
260 COLOR_SEQ = "\033[1;%dm"
252 COLOR_SEQ = "\033[1;%dm"
261 COLOR_SQL = YELLOW
253 COLOR_SQL = YELLOW
262 normal = '\x1b[0m'
254 normal = '\x1b[0m'
263 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
255 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
264
256
265 if configuration['debug']:
257 if configuration['debug']:
266 #attach events only for debug configuration
258 #attach events only for debug configuration
267
259
268 def before_cursor_execute(conn, cursor, statement,
260 def before_cursor_execute(conn, cursor, statement,
269 parameters, context, executemany):
261 parameters, context, executemany):
270 context._query_start_time = time.time()
262 context._query_start_time = time.time()
271 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
263 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
272
264
273
265
274 def after_cursor_execute(conn, cursor, statement,
266 def after_cursor_execute(conn, cursor, statement,
275 parameters, context, executemany):
267 parameters, context, executemany):
276 total = time.time() - context._query_start_time
268 total = time.time() - context._query_start_time
277 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
269 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
278
270
279 event.listen(engine, "before_cursor_execute",
271 event.listen(engine, "before_cursor_execute",
280 before_cursor_execute)
272 before_cursor_execute)
281 event.listen(engine, "after_cursor_execute",
273 event.listen(engine, "after_cursor_execute",
282 after_cursor_execute)
274 after_cursor_execute)
283
275
284 return engine
276 return engine
285
277
286
278
287 def age(curdate):
279 def age(curdate):
288 """
280 """
289 turns a datetime into an age string.
281 turns a datetime into an age string.
290
282
291 :param curdate: datetime object
283 :param curdate: datetime object
292 :rtype: unicode
284 :rtype: unicode
293 :returns: unicode words describing age
285 :returns: unicode words describing age
294 """
286 """
295
287
296 from datetime import datetime
288 from datetime import datetime
297 from webhelpers.date import time_ago_in_words
289 from webhelpers.date import time_ago_in_words
298
290
299 _ = lambda s:s
291 _ = lambda s:s
300
292
301 if not curdate:
293 if not curdate:
302 return ''
294 return ''
303
295
304 agescales = [(_(u"year"), 3600 * 24 * 365),
296 agescales = [(_(u"year"), 3600 * 24 * 365),
305 (_(u"month"), 3600 * 24 * 30),
297 (_(u"month"), 3600 * 24 * 30),
306 (_(u"day"), 3600 * 24),
298 (_(u"day"), 3600 * 24),
307 (_(u"hour"), 3600),
299 (_(u"hour"), 3600),
308 (_(u"minute"), 60),
300 (_(u"minute"), 60),
309 (_(u"second"), 1), ]
301 (_(u"second"), 1), ]
310
302
311 age = datetime.now() - curdate
303 age = datetime.now() - curdate
312 age_seconds = (age.days * agescales[2][1]) + age.seconds
304 age_seconds = (age.days * agescales[2][1]) + age.seconds
313 pos = 1
305 pos = 1
314 for scale in agescales:
306 for scale in agescales:
315 if scale[1] <= age_seconds:
307 if scale[1] <= age_seconds:
316 if pos == 6:pos = 5
308 if pos == 6:pos = 5
317 return '%s %s' % (time_ago_in_words(curdate,
309 return '%s %s' % (time_ago_in_words(curdate,
318 agescales[pos][0]), _('ago'))
310 agescales[pos][0]), _('ago'))
319 pos += 1
311 pos += 1
320
312
321 return _(u'just now')
313 return _(u'just now')
322
314
323
315
324 def uri_filter(uri):
316 def uri_filter(uri):
325 """
317 """
326 Removes user:password from given url string
318 Removes user:password from given url string
327
319
328 :param uri:
320 :param uri:
329 :rtype: unicode
321 :rtype: unicode
330 :returns: filtered list of strings
322 :returns: filtered list of strings
331 """
323 """
332 if not uri:
324 if not uri:
333 return ''
325 return ''
334
326
335 proto = ''
327 proto = ''
336
328
337 for pat in ('https://', 'http://'):
329 for pat in ('https://', 'http://'):
338 if uri.startswith(pat):
330 if uri.startswith(pat):
339 uri = uri[len(pat):]
331 uri = uri[len(pat):]
340 proto = pat
332 proto = pat
341 break
333 break
342
334
343 # remove passwords and username
335 # remove passwords and username
344 uri = uri[uri.find('@') + 1:]
336 uri = uri[uri.find('@') + 1:]
345
337
346 # get the port
338 # get the port
347 cred_pos = uri.find(':')
339 cred_pos = uri.find(':')
348 if cred_pos == -1:
340 if cred_pos == -1:
349 host, port = uri, None
341 host, port = uri, None
350 else:
342 else:
351 host, port = uri[:cred_pos], uri[cred_pos + 1:]
343 host, port = uri[:cred_pos], uri[cred_pos + 1:]
352
344
353 return filter(None, [proto, host, port])
345 return filter(None, [proto, host, port])
354
346
355
347
356 def credentials_filter(uri):
348 def credentials_filter(uri):
357 """
349 """
358 Returns a url with removed credentials
350 Returns a url with removed credentials
359
351
360 :param uri:
352 :param uri:
361 """
353 """
362
354
363 uri = uri_filter(uri)
355 uri = uri_filter(uri)
364 #check if we have port
356 #check if we have port
365 if len(uri) > 2 and uri[2]:
357 if len(uri) > 2 and uri[2]:
366 uri[2] = ':' + uri[2]
358 uri[2] = ':' + uri[2]
367
359
368 return ''.join(uri)
360 return ''.join(uri)
369
361
370 def get_changeset_safe(repo, rev):
362 def get_changeset_safe(repo, rev):
371 """
363 """
372 Safe version of get_changeset if this changeset doesn't exists for a
364 Safe version of get_changeset if this changeset doesn't exists for a
373 repo it returns a Dummy one instead
365 repo it returns a Dummy one instead
374
366
375 :param repo:
367 :param repo:
376 :param rev:
368 :param rev:
377 """
369 """
378 from vcs.backends.base import BaseRepository
370 from vcs.backends.base import BaseRepository
379 from vcs.exceptions import RepositoryError
371 from vcs.exceptions import RepositoryError
380 if not isinstance(repo, BaseRepository):
372 if not isinstance(repo, BaseRepository):
381 raise Exception('You must pass an Repository '
373 raise Exception('You must pass an Repository '
382 'object as first argument got %s', type(repo))
374 'object as first argument got %s', type(repo))
383
375
384 try:
376 try:
385 cs = repo.get_changeset(rev)
377 cs = repo.get_changeset(rev)
386 except RepositoryError:
378 except RepositoryError:
387 from rhodecode.lib.utils import EmptyChangeset
379 from rhodecode.lib.utils import EmptyChangeset
388 cs = EmptyChangeset(requested_revision=rev)
380 cs = EmptyChangeset(requested_revision=rev)
389 return cs No newline at end of file
381 return cs
@@ -1,413 +1,410 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.celerylib.tasks
3 rhodecode.lib.celerylib.tasks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode task modules, containing all task that suppose to be run
6 RhodeCode task modules, containing all task that suppose to be run
7 by celery daemon
7 by celery daemon
8
8
9 :created_on: Oct 6, 2010
9 :created_on: Oct 6, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 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 from celery.decorators import task
26 from celery.decorators import task
27
27
28 import os
28 import os
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from time import mktime
33 from time import mktime
34 from operator import itemgetter
34 from operator import itemgetter
35 from string import lower
35 from string import lower
36
36
37 from pylons import config, url
37 from pylons import config, url
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
42 __get_lockkey, LockHeld, DaemonLock
42 __get_lockkey, LockHeld, DaemonLock
43 from rhodecode.lib.helpers import person
43 from rhodecode.lib.helpers import person
44 from rhodecode.lib.smtp_mailer import SmtpMailer
44 from rhodecode.lib.smtp_mailer import SmtpMailer
45 from rhodecode.lib.utils import add_cache
45 from rhodecode.lib.utils import add_cache
46 from rhodecode.lib.odict import OrderedDict
46 from rhodecode.lib.compat import json, OrderedDict
47
47 from rhodecode.model import init_model
48 from rhodecode.model import init_model
48 from rhodecode.model import meta
49 from rhodecode.model import meta
49 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
50 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
50
51
51 from vcs.backends import get_repo
52 from vcs.backends import get_repo
52
53
53 from sqlalchemy import engine_from_config
54 from sqlalchemy import engine_from_config
54
55
55 add_cache(config)
56 add_cache(config)
56
57
57 try:
58
58 import json
59 except ImportError:
60 #python 2.5 compatibility
61 import simplejson as json
62
59
63 __all__ = ['whoosh_index', 'get_commits_stats',
60 __all__ = ['whoosh_index', 'get_commits_stats',
64 'reset_user_password', 'send_email']
61 'reset_user_password', 'send_email']
65
62
66 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
63 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
67
64
68
65
69 def get_session():
66 def get_session():
70 if CELERY_ON:
67 if CELERY_ON:
71 engine = engine_from_config(config, 'sqlalchemy.db1.')
68 engine = engine_from_config(config, 'sqlalchemy.db1.')
72 init_model(engine)
69 init_model(engine)
73 sa = meta.Session()
70 sa = meta.Session()
74 return sa
71 return sa
75
72
76
73
77 def get_repos_path():
74 def get_repos_path():
78 sa = get_session()
75 sa = get_session()
79 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
76 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
80 return q.ui_value
77 return q.ui_value
81
78
82
79
83 @task(ignore_result=True)
80 @task(ignore_result=True)
84 @locked_task
81 @locked_task
85 def whoosh_index(repo_location, full_index):
82 def whoosh_index(repo_location, full_index):
86 #log = whoosh_index.get_logger()
83 #log = whoosh_index.get_logger()
87 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
88 index_location = config['index_dir']
85 index_location = config['index_dir']
89 WhooshIndexingDaemon(index_location=index_location,
86 WhooshIndexingDaemon(index_location=index_location,
90 repo_location=repo_location, sa=get_session())\
87 repo_location=repo_location, sa=get_session())\
91 .run(full_index=full_index)
88 .run(full_index=full_index)
92
89
93
90
94 @task(ignore_result=True)
91 @task(ignore_result=True)
95 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
92 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
96 try:
93 try:
97 log = get_commits_stats.get_logger()
94 log = get_commits_stats.get_logger()
98 except:
95 except:
99 log = logging.getLogger(__name__)
96 log = logging.getLogger(__name__)
100
97
101 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
102 ts_max_y)
99 ts_max_y)
103 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
100 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
104 log.info('running task with lockkey %s', lockkey)
101 log.info('running task with lockkey %s', lockkey)
105 try:
102 try:
106 lock = l = DaemonLock(jn(lockkey_path, lockkey))
103 lock = l = DaemonLock(jn(lockkey_path, lockkey))
107
104
108 #for js data compatibilty cleans the key for person from '
105 #for js data compatibilty cleans the key for person from '
109 akc = lambda k: person(k).replace('"', "")
106 akc = lambda k: person(k).replace('"', "")
110
107
111 co_day_auth_aggr = {}
108 co_day_auth_aggr = {}
112 commits_by_day_aggregate = {}
109 commits_by_day_aggregate = {}
113 repos_path = get_repos_path()
110 repos_path = get_repos_path()
114 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
111 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
115 repo_size = len(repo.revisions)
112 repo_size = len(repo.revisions)
116 #return if repo have no revisions
113 #return if repo have no revisions
117 if repo_size < 1:
114 if repo_size < 1:
118 lock.release()
115 lock.release()
119 return True
116 return True
120
117
121 skip_date_limit = True
118 skip_date_limit = True
122 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
119 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
123 last_rev = 0
120 last_rev = 0
124 last_cs = None
121 last_cs = None
125 timegetter = itemgetter('time')
122 timegetter = itemgetter('time')
126
123
127 sa = get_session()
124 sa = get_session()
128
125
129 dbrepo = sa.query(Repository)\
126 dbrepo = sa.query(Repository)\
130 .filter(Repository.repo_name == repo_name).scalar()
127 .filter(Repository.repo_name == repo_name).scalar()
131 cur_stats = sa.query(Statistics)\
128 cur_stats = sa.query(Statistics)\
132 .filter(Statistics.repository == dbrepo).scalar()
129 .filter(Statistics.repository == dbrepo).scalar()
133
130
134 if cur_stats is not None:
131 if cur_stats is not None:
135 last_rev = cur_stats.stat_on_revision
132 last_rev = cur_stats.stat_on_revision
136
133
137 if last_rev == repo.get_changeset().revision and repo_size > 1:
134 if last_rev == repo.get_changeset().revision and repo_size > 1:
138 #pass silently without any work if we're not on first revision or
135 #pass silently without any work if we're not on first revision or
139 #current state of parsing revision(from db marker) is the
136 #current state of parsing revision(from db marker) is the
140 #last revision
137 #last revision
141 lock.release()
138 lock.release()
142 return True
139 return True
143
140
144 if cur_stats:
141 if cur_stats:
145 commits_by_day_aggregate = OrderedDict(json.loads(
142 commits_by_day_aggregate = OrderedDict(json.loads(
146 cur_stats.commit_activity_combined))
143 cur_stats.commit_activity_combined))
147 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
144 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
148
145
149 log.debug('starting parsing %s', parse_limit)
146 log.debug('starting parsing %s', parse_limit)
150 lmktime = mktime
147 lmktime = mktime
151
148
152 last_rev = last_rev + 1 if last_rev > 0 else last_rev
149 last_rev = last_rev + 1 if last_rev > 0 else last_rev
153
150
154 for cs in repo[last_rev:last_rev + parse_limit]:
151 for cs in repo[last_rev:last_rev + parse_limit]:
155 last_cs = cs # remember last parsed changeset
152 last_cs = cs # remember last parsed changeset
156 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
153 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
157 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
154 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
158
155
159 if akc(cs.author) in co_day_auth_aggr:
156 if akc(cs.author) in co_day_auth_aggr:
160 try:
157 try:
161 l = [timegetter(x) for x in
158 l = [timegetter(x) for x in
162 co_day_auth_aggr[akc(cs.author)]['data']]
159 co_day_auth_aggr[akc(cs.author)]['data']]
163 time_pos = l.index(k)
160 time_pos = l.index(k)
164 except ValueError:
161 except ValueError:
165 time_pos = False
162 time_pos = False
166
163
167 if time_pos >= 0 and time_pos is not False:
164 if time_pos >= 0 and time_pos is not False:
168
165
169 datadict = \
166 datadict = \
170 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
167 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
171
168
172 datadict["commits"] += 1
169 datadict["commits"] += 1
173 datadict["added"] += len(cs.added)
170 datadict["added"] += len(cs.added)
174 datadict["changed"] += len(cs.changed)
171 datadict["changed"] += len(cs.changed)
175 datadict["removed"] += len(cs.removed)
172 datadict["removed"] += len(cs.removed)
176
173
177 else:
174 else:
178 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
175 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
179
176
180 datadict = {"time": k,
177 datadict = {"time": k,
181 "commits": 1,
178 "commits": 1,
182 "added": len(cs.added),
179 "added": len(cs.added),
183 "changed": len(cs.changed),
180 "changed": len(cs.changed),
184 "removed": len(cs.removed),
181 "removed": len(cs.removed),
185 }
182 }
186 co_day_auth_aggr[akc(cs.author)]['data']\
183 co_day_auth_aggr[akc(cs.author)]['data']\
187 .append(datadict)
184 .append(datadict)
188
185
189 else:
186 else:
190 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
187 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
191 co_day_auth_aggr[akc(cs.author)] = {
188 co_day_auth_aggr[akc(cs.author)] = {
192 "label": akc(cs.author),
189 "label": akc(cs.author),
193 "data": [{"time":k,
190 "data": [{"time":k,
194 "commits":1,
191 "commits":1,
195 "added":len(cs.added),
192 "added":len(cs.added),
196 "changed":len(cs.changed),
193 "changed":len(cs.changed),
197 "removed":len(cs.removed),
194 "removed":len(cs.removed),
198 }],
195 }],
199 "schema": ["commits"],
196 "schema": ["commits"],
200 }
197 }
201
198
202 #gather all data by day
199 #gather all data by day
203 if k in commits_by_day_aggregate:
200 if k in commits_by_day_aggregate:
204 commits_by_day_aggregate[k] += 1
201 commits_by_day_aggregate[k] += 1
205 else:
202 else:
206 commits_by_day_aggregate[k] = 1
203 commits_by_day_aggregate[k] = 1
207
204
208 overview_data = sorted(commits_by_day_aggregate.items(),
205 overview_data = sorted(commits_by_day_aggregate.items(),
209 key=itemgetter(0))
206 key=itemgetter(0))
210
207
211 if not co_day_auth_aggr:
208 if not co_day_auth_aggr:
212 co_day_auth_aggr[akc(repo.contact)] = {
209 co_day_auth_aggr[akc(repo.contact)] = {
213 "label": akc(repo.contact),
210 "label": akc(repo.contact),
214 "data": [0, 1],
211 "data": [0, 1],
215 "schema": ["commits"],
212 "schema": ["commits"],
216 }
213 }
217
214
218 stats = cur_stats if cur_stats else Statistics()
215 stats = cur_stats if cur_stats else Statistics()
219 stats.commit_activity = json.dumps(co_day_auth_aggr)
216 stats.commit_activity = json.dumps(co_day_auth_aggr)
220 stats.commit_activity_combined = json.dumps(overview_data)
217 stats.commit_activity_combined = json.dumps(overview_data)
221
218
222 log.debug('last revison %s', last_rev)
219 log.debug('last revison %s', last_rev)
223 leftovers = len(repo.revisions[last_rev:])
220 leftovers = len(repo.revisions[last_rev:])
224 log.debug('revisions to parse %s', leftovers)
221 log.debug('revisions to parse %s', leftovers)
225
222
226 if last_rev == 0 or leftovers < parse_limit:
223 if last_rev == 0 or leftovers < parse_limit:
227 log.debug('getting code trending stats')
224 log.debug('getting code trending stats')
228 stats.languages = json.dumps(__get_codes_stats(repo_name))
225 stats.languages = json.dumps(__get_codes_stats(repo_name))
229
226
230 try:
227 try:
231 stats.repository = dbrepo
228 stats.repository = dbrepo
232 stats.stat_on_revision = last_cs.revision if last_cs else 0
229 stats.stat_on_revision = last_cs.revision if last_cs else 0
233 sa.add(stats)
230 sa.add(stats)
234 sa.commit()
231 sa.commit()
235 except:
232 except:
236 log.error(traceback.format_exc())
233 log.error(traceback.format_exc())
237 sa.rollback()
234 sa.rollback()
238 lock.release()
235 lock.release()
239 return False
236 return False
240
237
241 #final release
238 #final release
242 lock.release()
239 lock.release()
243
240
244 #execute another task if celery is enabled
241 #execute another task if celery is enabled
245 if len(repo.revisions) > 1 and CELERY_ON:
242 if len(repo.revisions) > 1 and CELERY_ON:
246 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
243 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
247 return True
244 return True
248 except LockHeld:
245 except LockHeld:
249 log.info('LockHeld')
246 log.info('LockHeld')
250 return 'Task with key %s already running' % lockkey
247 return 'Task with key %s already running' % lockkey
251
248
252 @task(ignore_result=True)
249 @task(ignore_result=True)
253 def send_password_link(user_email):
250 def send_password_link(user_email):
254 try:
251 try:
255 log = reset_user_password.get_logger()
252 log = reset_user_password.get_logger()
256 except:
253 except:
257 log = logging.getLogger(__name__)
254 log = logging.getLogger(__name__)
258
255
259 from rhodecode.lib import auth
256 from rhodecode.lib import auth
260 from rhodecode.model.db import User
257 from rhodecode.model.db import User
261
258
262 try:
259 try:
263 sa = get_session()
260 sa = get_session()
264 user = sa.query(User).filter(User.email == user_email).scalar()
261 user = sa.query(User).filter(User.email == user_email).scalar()
265
262
266 if user:
263 if user:
267 link = url('reset_password_confirmation', key=user.api_key,
264 link = url('reset_password_confirmation', key=user.api_key,
268 qualified=True)
265 qualified=True)
269 tmpl = """
266 tmpl = """
270 Hello %s
267 Hello %s
271
268
272 We received a request to create a new password for your account.
269 We received a request to create a new password for your account.
273
270
274 You can generate it by clicking following URL:
271 You can generate it by clicking following URL:
275
272
276 %s
273 %s
277
274
278 If you didn't request new password please ignore this email.
275 If you didn't request new password please ignore this email.
279 """
276 """
280 run_task(send_email, user_email,
277 run_task(send_email, user_email,
281 "RhodeCode password reset link",
278 "RhodeCode password reset link",
282 tmpl % (user.short_contact, link))
279 tmpl % (user.short_contact, link))
283 log.info('send new password mail to %s', user_email)
280 log.info('send new password mail to %s', user_email)
284
281
285 except:
282 except:
286 log.error('Failed to update user password')
283 log.error('Failed to update user password')
287 log.error(traceback.format_exc())
284 log.error(traceback.format_exc())
288 return False
285 return False
289
286
290 return True
287 return True
291
288
292 @task(ignore_result=True)
289 @task(ignore_result=True)
293 def reset_user_password(user_email):
290 def reset_user_password(user_email):
294 try:
291 try:
295 log = reset_user_password.get_logger()
292 log = reset_user_password.get_logger()
296 except:
293 except:
297 log = logging.getLogger(__name__)
294 log = logging.getLogger(__name__)
298
295
299 from rhodecode.lib import auth
296 from rhodecode.lib import auth
300 from rhodecode.model.db import User
297 from rhodecode.model.db import User
301
298
302 try:
299 try:
303 try:
300 try:
304 sa = get_session()
301 sa = get_session()
305 user = sa.query(User).filter(User.email == user_email).scalar()
302 user = sa.query(User).filter(User.email == user_email).scalar()
306 new_passwd = auth.PasswordGenerator().gen_password(8,
303 new_passwd = auth.PasswordGenerator().gen_password(8,
307 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
304 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
308 if user:
305 if user:
309 user.password = auth.get_crypt_password(new_passwd)
306 user.password = auth.get_crypt_password(new_passwd)
310 user.api_key = auth.generate_api_key(user.username)
307 user.api_key = auth.generate_api_key(user.username)
311 sa.add(user)
308 sa.add(user)
312 sa.commit()
309 sa.commit()
313 log.info('change password for %s', user_email)
310 log.info('change password for %s', user_email)
314 if new_passwd is None:
311 if new_passwd is None:
315 raise Exception('unable to generate new password')
312 raise Exception('unable to generate new password')
316
313
317 except:
314 except:
318 log.error(traceback.format_exc())
315 log.error(traceback.format_exc())
319 sa.rollback()
316 sa.rollback()
320
317
321 run_task(send_email, user_email,
318 run_task(send_email, user_email,
322 "Your new RhodeCode password",
319 "Your new RhodeCode password",
323 'Your new RhodeCode password:%s' % (new_passwd))
320 'Your new RhodeCode password:%s' % (new_passwd))
324 log.info('send new password mail to %s', user_email)
321 log.info('send new password mail to %s', user_email)
325
322
326 except:
323 except:
327 log.error('Failed to update user password')
324 log.error('Failed to update user password')
328 log.error(traceback.format_exc())
325 log.error(traceback.format_exc())
329
326
330 return True
327 return True
331
328
332
329
333 @task(ignore_result=True)
330 @task(ignore_result=True)
334 def send_email(recipients, subject, body):
331 def send_email(recipients, subject, body):
335 """
332 """
336 Sends an email with defined parameters from the .ini files.
333 Sends an email with defined parameters from the .ini files.
337
334
338 :param recipients: list of recipients, it this is empty the defined email
335 :param recipients: list of recipients, it this is empty the defined email
339 address from field 'email_to' is used instead
336 address from field 'email_to' is used instead
340 :param subject: subject of the mail
337 :param subject: subject of the mail
341 :param body: body of the mail
338 :param body: body of the mail
342 """
339 """
343 try:
340 try:
344 log = send_email.get_logger()
341 log = send_email.get_logger()
345 except:
342 except:
346 log = logging.getLogger(__name__)
343 log = logging.getLogger(__name__)
347
344
348 email_config = config
345 email_config = config
349
346
350 if not recipients:
347 if not recipients:
351 recipients = [email_config.get('email_to')]
348 recipients = [email_config.get('email_to')]
352
349
353 mail_from = email_config.get('app_email_from')
350 mail_from = email_config.get('app_email_from')
354 user = email_config.get('smtp_username')
351 user = email_config.get('smtp_username')
355 passwd = email_config.get('smtp_password')
352 passwd = email_config.get('smtp_password')
356 mail_server = email_config.get('smtp_server')
353 mail_server = email_config.get('smtp_server')
357 mail_port = email_config.get('smtp_port')
354 mail_port = email_config.get('smtp_port')
358 tls = str2bool(email_config.get('smtp_use_tls'))
355 tls = str2bool(email_config.get('smtp_use_tls'))
359 ssl = str2bool(email_config.get('smtp_use_ssl'))
356 ssl = str2bool(email_config.get('smtp_use_ssl'))
360 debug = str2bool(config.get('debug'))
357 debug = str2bool(config.get('debug'))
361
358
362 try:
359 try:
363 m = SmtpMailer(mail_from, user, passwd, mail_server,
360 m = SmtpMailer(mail_from, user, passwd, mail_server,
364 mail_port, ssl, tls, debug=debug)
361 mail_port, ssl, tls, debug=debug)
365 m.send(recipients, subject, body)
362 m.send(recipients, subject, body)
366 except:
363 except:
367 log.error('Mail sending failed')
364 log.error('Mail sending failed')
368 log.error(traceback.format_exc())
365 log.error(traceback.format_exc())
369 return False
366 return False
370 return True
367 return True
371
368
372
369
373 @task(ignore_result=True)
370 @task(ignore_result=True)
374 def create_repo_fork(form_data, cur_user):
371 def create_repo_fork(form_data, cur_user):
375 from rhodecode.model.repo import RepoModel
372 from rhodecode.model.repo import RepoModel
376 from vcs import get_backend
373 from vcs import get_backend
377
374
378 try:
375 try:
379 log = create_repo_fork.get_logger()
376 log = create_repo_fork.get_logger()
380 except:
377 except:
381 log = logging.getLogger(__name__)
378 log = logging.getLogger(__name__)
382
379
383 repo_model = RepoModel(get_session())
380 repo_model = RepoModel(get_session())
384 repo_model.create(form_data, cur_user, just_db=True, fork=True)
381 repo_model.create(form_data, cur_user, just_db=True, fork=True)
385 repo_name = form_data['repo_name']
382 repo_name = form_data['repo_name']
386 repos_path = get_repos_path()
383 repos_path = get_repos_path()
387 repo_path = os.path.join(repos_path, repo_name)
384 repo_path = os.path.join(repos_path, repo_name)
388 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
385 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
389 alias = form_data['repo_type']
386 alias = form_data['repo_type']
390
387
391 log.info('creating repo fork %s as %s', repo_name, repo_path)
388 log.info('creating repo fork %s as %s', repo_name, repo_path)
392 backend = get_backend(alias)
389 backend = get_backend(alias)
393 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
390 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
394
391
395
392
396 def __get_codes_stats(repo_name):
393 def __get_codes_stats(repo_name):
397 repos_path = get_repos_path()
394 repos_path = get_repos_path()
398 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
395 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
399 tip = repo.get_changeset()
396 tip = repo.get_changeset()
400 code_stats = {}
397 code_stats = {}
401
398
402 def aggregate(cs):
399 def aggregate(cs):
403 for f in cs[2]:
400 for f in cs[2]:
404 ext = lower(f.extension)
401 ext = lower(f.extension)
405 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
402 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
406 if ext in code_stats:
403 if ext in code_stats:
407 code_stats[ext] += 1
404 code_stats[ext] += 1
408 else:
405 else:
409 code_stats[ext] = 1
406 code_stats[ext] = 1
410
407
411 map(aggregate, tip.walk('/'))
408 map(aggregate, tip.walk('/'))
412
409
413 return code_stats or {}
410 return code_stats or {}
@@ -1,979 +1,980 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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 modify
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
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from datetime import date
30 from datetime import date
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
35 from sqlalchemy.orm.interfaces import MapperExtension
35 from sqlalchemy.orm.interfaces import MapperExtension
36
36
37 from beaker.cache import cache_region, region_invalidate
37 from beaker.cache import cache_region, region_invalidate
38
38
39 from vcs import get_backend
39 from vcs import get_backend
40 from vcs.utils.helpers import get_scm
40 from vcs.utils.helpers import get_scm
41 from vcs.exceptions import VCSError
41 from vcs.exceptions import VCSError
42 from vcs.utils.lazy import LazyProperty
42 from vcs.utils.lazy import LazyProperty
43
43
44 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
45 generate_api_key
44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
46 from rhodecode.lib.exceptions import UsersGroupsAssignedException
45 from rhodecode.lib import str2bool, json, safe_str, get_changeset_safe,\
47 from rhodecode.lib.compat import json
46 generate_api_key
47
48
48 from rhodecode.model.meta import Base, Session
49 from rhodecode.model.meta import Base, Session
49 from rhodecode.model.caching_query import FromCache
50 from rhodecode.model.caching_query import FromCache
50
51
51 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
52
53
53 #==============================================================================
54 #==============================================================================
54 # BASE CLASSES
55 # BASE CLASSES
55 #==============================================================================
56 #==============================================================================
56
57
57 class ModelSerializer(json.JSONEncoder):
58 class ModelSerializer(json.JSONEncoder):
58 """
59 """
59 Simple Serializer for JSON,
60 Simple Serializer for JSON,
60
61
61 usage::
62 usage::
62
63
63 to make object customized for serialization implement a __json__
64 to make object customized for serialization implement a __json__
64 method that will return a dict for serialization into json
65 method that will return a dict for serialization into json
65
66
66 example::
67 example::
67
68
68 class Task(object):
69 class Task(object):
69
70
70 def __init__(self, name, value):
71 def __init__(self, name, value):
71 self.name = name
72 self.name = name
72 self.value = value
73 self.value = value
73
74
74 def __json__(self):
75 def __json__(self):
75 return dict(name=self.name,
76 return dict(name=self.name,
76 value=self.value)
77 value=self.value)
77
78
78 """
79 """
79
80
80 def default(self, obj):
81 def default(self, obj):
81
82
82 if hasattr(obj, '__json__'):
83 if hasattr(obj, '__json__'):
83 return obj.__json__()
84 return obj.__json__()
84 else:
85 else:
85 return json.JSONEncoder.default(self, obj)
86 return json.JSONEncoder.default(self, obj)
86
87
87 class BaseModel(object):
88 class BaseModel(object):
88 """Base Model for all classess
89 """Base Model for all classess
89
90
90 """
91 """
91
92
92 @classmethod
93 @classmethod
93 def _get_keys(cls):
94 def _get_keys(cls):
94 """return column names for this model """
95 """return column names for this model """
95 return class_mapper(cls).c.keys()
96 return class_mapper(cls).c.keys()
96
97
97 def get_dict(self):
98 def get_dict(self):
98 """return dict with keys and values corresponding
99 """return dict with keys and values corresponding
99 to this model data """
100 to this model data """
100
101
101 d = {}
102 d = {}
102 for k in self._get_keys():
103 for k in self._get_keys():
103 d[k] = getattr(self, k)
104 d[k] = getattr(self, k)
104 return d
105 return d
105
106
106 def get_appstruct(self):
107 def get_appstruct(self):
107 """return list with keys and values tupples corresponding
108 """return list with keys and values tupples corresponding
108 to this model data """
109 to this model data """
109
110
110 l = []
111 l = []
111 for k in self._get_keys():
112 for k in self._get_keys():
112 l.append((k, getattr(self, k),))
113 l.append((k, getattr(self, k),))
113 return l
114 return l
114
115
115 def populate_obj(self, populate_dict):
116 def populate_obj(self, populate_dict):
116 """populate model with data from given populate_dict"""
117 """populate model with data from given populate_dict"""
117
118
118 for k in self._get_keys():
119 for k in self._get_keys():
119 if k in populate_dict:
120 if k in populate_dict:
120 setattr(self, k, populate_dict[k])
121 setattr(self, k, populate_dict[k])
121
122
122 @classmethod
123 @classmethod
123 def query(cls):
124 def query(cls):
124 return Session.query(cls)
125 return Session.query(cls)
125
126
126 @classmethod
127 @classmethod
127 def get(cls, id_):
128 def get(cls, id_):
128 return Session.query(cls).get(id_)
129 return Session.query(cls).get(id_)
129
130
130 @classmethod
131 @classmethod
131 def delete(cls, id_):
132 def delete(cls, id_):
132 obj = Session.query(cls).get(id_)
133 obj = Session.query(cls).get(id_)
133 Session.delete(obj)
134 Session.delete(obj)
134 Session.commit()
135 Session.commit()
135
136
136
137
137 class RhodeCodeSettings(Base, BaseModel):
138 class RhodeCodeSettings(Base, BaseModel):
138 __tablename__ = 'rhodecode_settings'
139 __tablename__ = 'rhodecode_settings'
139 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
140 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
140 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
141 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
141 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
142 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
142 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143
144
144 def __init__(self, k='', v=''):
145 def __init__(self, k='', v=''):
145 self.app_settings_name = k
146 self.app_settings_name = k
146 self.app_settings_value = v
147 self.app_settings_value = v
147
148
148 def __repr__(self):
149 def __repr__(self):
149 return "<%s('%s:%s')>" % (self.__class__.__name__,
150 return "<%s('%s:%s')>" % (self.__class__.__name__,
150 self.app_settings_name, self.app_settings_value)
151 self.app_settings_name, self.app_settings_value)
151
152
152
153
153 @classmethod
154 @classmethod
154 def get_by_name(cls, ldap_key):
155 def get_by_name(cls, ldap_key):
155 return Session.query(cls)\
156 return Session.query(cls)\
156 .filter(cls.app_settings_name == ldap_key).scalar()
157 .filter(cls.app_settings_name == ldap_key).scalar()
157
158
158 @classmethod
159 @classmethod
159 def get_app_settings(cls, cache=False):
160 def get_app_settings(cls, cache=False):
160
161
161 ret = Session.query(cls)
162 ret = Session.query(cls)
162
163
163 if cache:
164 if cache:
164 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
165 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
165
166
166 if not ret:
167 if not ret:
167 raise Exception('Could not get application settings !')
168 raise Exception('Could not get application settings !')
168 settings = {}
169 settings = {}
169 for each in ret:
170 for each in ret:
170 settings['rhodecode_' + each.app_settings_name] = \
171 settings['rhodecode_' + each.app_settings_name] = \
171 each.app_settings_value
172 each.app_settings_value
172
173
173 return settings
174 return settings
174
175
175 @classmethod
176 @classmethod
176 def get_ldap_settings(cls, cache=False):
177 def get_ldap_settings(cls, cache=False):
177 ret = Session.query(cls)\
178 ret = Session.query(cls)\
178 .filter(cls.app_settings_name.startswith('ldap_'))\
179 .filter(cls.app_settings_name.startswith('ldap_'))\
179 .all()
180 .all()
180 fd = {}
181 fd = {}
181 for row in ret:
182 for row in ret:
182 fd.update({row.app_settings_name:row.app_settings_value})
183 fd.update({row.app_settings_name:row.app_settings_value})
183
184
184 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
185 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
185
186
186 return fd
187 return fd
187
188
188
189
189 class RhodeCodeUi(Base, BaseModel):
190 class RhodeCodeUi(Base, BaseModel):
190 __tablename__ = 'rhodecode_ui'
191 __tablename__ = 'rhodecode_ui'
191 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
192 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
192
193
193 HOOK_UPDATE = 'changegroup.update'
194 HOOK_UPDATE = 'changegroup.update'
194 HOOK_REPO_SIZE = 'changegroup.repo_size'
195 HOOK_REPO_SIZE = 'changegroup.repo_size'
195 HOOK_PUSH = 'pretxnchangegroup.push_logger'
196 HOOK_PUSH = 'pretxnchangegroup.push_logger'
196 HOOK_PULL = 'preoutgoing.pull_logger'
197 HOOK_PULL = 'preoutgoing.pull_logger'
197
198
198 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
199 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
199 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
200 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
200 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
201 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
201 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
202 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
202 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
203 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
203
204
204
205
205 @classmethod
206 @classmethod
206 def get_by_key(cls, key):
207 def get_by_key(cls, key):
207 return Session.query(cls).filter(cls.ui_key == key)
208 return Session.query(cls).filter(cls.ui_key == key)
208
209
209
210
210 @classmethod
211 @classmethod
211 def get_builtin_hooks(cls):
212 def get_builtin_hooks(cls):
212 q = cls.query()
213 q = cls.query()
213 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
214 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
214 cls.HOOK_REPO_SIZE,
215 cls.HOOK_REPO_SIZE,
215 cls.HOOK_PUSH, cls.HOOK_PULL]))
216 cls.HOOK_PUSH, cls.HOOK_PULL]))
216 return q.all()
217 return q.all()
217
218
218 @classmethod
219 @classmethod
219 def get_custom_hooks(cls):
220 def get_custom_hooks(cls):
220 q = cls.query()
221 q = cls.query()
221 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
222 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
222 cls.HOOK_REPO_SIZE,
223 cls.HOOK_REPO_SIZE,
223 cls.HOOK_PUSH, cls.HOOK_PULL]))
224 cls.HOOK_PUSH, cls.HOOK_PULL]))
224 q = q.filter(cls.ui_section == 'hooks')
225 q = q.filter(cls.ui_section == 'hooks')
225 return q.all()
226 return q.all()
226
227
227 @classmethod
228 @classmethod
228 def create_or_update_hook(cls, key, val):
229 def create_or_update_hook(cls, key, val):
229 new_ui = cls.get_by_key(key).scalar() or cls()
230 new_ui = cls.get_by_key(key).scalar() or cls()
230 new_ui.ui_section = 'hooks'
231 new_ui.ui_section = 'hooks'
231 new_ui.ui_active = True
232 new_ui.ui_active = True
232 new_ui.ui_key = key
233 new_ui.ui_key = key
233 new_ui.ui_value = val
234 new_ui.ui_value = val
234
235
235 Session.add(new_ui)
236 Session.add(new_ui)
236 Session.commit()
237 Session.commit()
237
238
238
239
239 class User(Base, BaseModel):
240 class User(Base, BaseModel):
240 __tablename__ = 'users'
241 __tablename__ = 'users'
241 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
242 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
242 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
243 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
243 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
244 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
244 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
246 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
246 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
247 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
247 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
249 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
249 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
251 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
251 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
253 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
253
254
254 user_log = relationship('UserLog', cascade='all')
255 user_log = relationship('UserLog', cascade='all')
255 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
256 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
256
257
257 repositories = relationship('Repository')
258 repositories = relationship('Repository')
258 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
259 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
259 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
260 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
260
261
261 group_member = relationship('UsersGroupMember', cascade='all')
262 group_member = relationship('UsersGroupMember', cascade='all')
262
263
263 @property
264 @property
264 def full_contact(self):
265 def full_contact(self):
265 return '%s %s <%s>' % (self.name, self.lastname, self.email)
266 return '%s %s <%s>' % (self.name, self.lastname, self.email)
266
267
267 @property
268 @property
268 def short_contact(self):
269 def short_contact(self):
269 return '%s %s' % (self.name, self.lastname)
270 return '%s %s' % (self.name, self.lastname)
270
271
271 @property
272 @property
272 def is_admin(self):
273 def is_admin(self):
273 return self.admin
274 return self.admin
274
275
275 def __repr__(self):
276 def __repr__(self):
276 try:
277 try:
277 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
278 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
278 self.user_id, self.username)
279 self.user_id, self.username)
279 except:
280 except:
280 return self.__class__.__name__
281 return self.__class__.__name__
281
282
282 @classmethod
283 @classmethod
283 def by_username(cls, username, case_insensitive=False):
284 def by_username(cls, username, case_insensitive=False):
284 if case_insensitive:
285 if case_insensitive:
285 return Session.query(cls).filter(cls.username.like(username)).one()
286 return Session.query(cls).filter(cls.username.like(username)).one()
286 else:
287 else:
287 return Session.query(cls).filter(cls.username == username).one()
288 return Session.query(cls).filter(cls.username == username).one()
288
289
289 @classmethod
290 @classmethod
290 def get_by_api_key(cls, api_key):
291 def get_by_api_key(cls, api_key):
291 return Session.query(cls).filter(cls.api_key == api_key).one()
292 return Session.query(cls).filter(cls.api_key == api_key).one()
292
293
293
294
294 def update_lastlogin(self):
295 def update_lastlogin(self):
295 """Update user lastlogin"""
296 """Update user lastlogin"""
296
297
297 self.last_login = datetime.datetime.now()
298 self.last_login = datetime.datetime.now()
298 Session.add(self)
299 Session.add(self)
299 Session.commit()
300 Session.commit()
300 log.debug('updated user %s lastlogin', self.username)
301 log.debug('updated user %s lastlogin', self.username)
301
302
302 @classmethod
303 @classmethod
303 def create(cls, form_data):
304 def create(cls, form_data):
304 from rhodecode.lib.auth import get_crypt_password
305 from rhodecode.lib.auth import get_crypt_password
305
306
306 try:
307 try:
307 new_user = cls()
308 new_user = cls()
308 for k, v in form_data.items():
309 for k, v in form_data.items():
309 if k == 'password':
310 if k == 'password':
310 v = get_crypt_password(v)
311 v = get_crypt_password(v)
311 setattr(new_user, k, v)
312 setattr(new_user, k, v)
312
313
313 new_user.api_key = generate_api_key(form_data['username'])
314 new_user.api_key = generate_api_key(form_data['username'])
314 Session.add(new_user)
315 Session.add(new_user)
315 Session.commit()
316 Session.commit()
316 return new_user
317 return new_user
317 except:
318 except:
318 log.error(traceback.format_exc())
319 log.error(traceback.format_exc())
319 Session.rollback()
320 Session.rollback()
320 raise
321 raise
321
322
322 class UserLog(Base, BaseModel):
323 class UserLog(Base, BaseModel):
323 __tablename__ = 'user_logs'
324 __tablename__ = 'user_logs'
324 __table_args__ = {'extend_existing':True}
325 __table_args__ = {'extend_existing':True}
325 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
326 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
326 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
327 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
328 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
328 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
332 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
332
333
333 @property
334 @property
334 def action_as_day(self):
335 def action_as_day(self):
335 return date(*self.action_date.timetuple()[:3])
336 return date(*self.action_date.timetuple()[:3])
336
337
337 user = relationship('User')
338 user = relationship('User')
338 repository = relationship('Repository')
339 repository = relationship('Repository')
339
340
340
341
341 class UsersGroup(Base, BaseModel):
342 class UsersGroup(Base, BaseModel):
342 __tablename__ = 'users_groups'
343 __tablename__ = 'users_groups'
343 __table_args__ = {'extend_existing':True}
344 __table_args__ = {'extend_existing':True}
344
345
345 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
346 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
346 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
347 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
347 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
348 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
348
349
349 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
350 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
350
351
351 def __repr__(self):
352 def __repr__(self):
352 return '<userGroup(%s)>' % (self.users_group_name)
353 return '<userGroup(%s)>' % (self.users_group_name)
353
354
354 @classmethod
355 @classmethod
355 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
356 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
356 if case_insensitive:
357 if case_insensitive:
357 gr = Session.query(cls)\
358 gr = Session.query(cls)\
358 .filter(cls.users_group_name.ilike(group_name))
359 .filter(cls.users_group_name.ilike(group_name))
359 else:
360 else:
360 gr = Session.query(UsersGroup)\
361 gr = Session.query(UsersGroup)\
361 .filter(UsersGroup.users_group_name == group_name)
362 .filter(UsersGroup.users_group_name == group_name)
362 if cache:
363 if cache:
363 gr = gr.options(FromCache("sql_cache_short",
364 gr = gr.options(FromCache("sql_cache_short",
364 "get_user_%s" % group_name))
365 "get_user_%s" % group_name))
365 return gr.scalar()
366 return gr.scalar()
366
367
367
368
368 @classmethod
369 @classmethod
369 def get(cls, users_group_id, cache=False):
370 def get(cls, users_group_id, cache=False):
370 users_group = Session.query(cls)
371 users_group = Session.query(cls)
371 if cache:
372 if cache:
372 users_group = users_group.options(FromCache("sql_cache_short",
373 users_group = users_group.options(FromCache("sql_cache_short",
373 "get_users_group_%s" % users_group_id))
374 "get_users_group_%s" % users_group_id))
374 return users_group.get(users_group_id)
375 return users_group.get(users_group_id)
375
376
376 @classmethod
377 @classmethod
377 def create(cls, form_data):
378 def create(cls, form_data):
378 try:
379 try:
379 new_users_group = cls()
380 new_users_group = cls()
380 for k, v in form_data.items():
381 for k, v in form_data.items():
381 setattr(new_users_group, k, v)
382 setattr(new_users_group, k, v)
382
383
383 Session.add(new_users_group)
384 Session.add(new_users_group)
384 Session.commit()
385 Session.commit()
385 return new_users_group
386 return new_users_group
386 except:
387 except:
387 log.error(traceback.format_exc())
388 log.error(traceback.format_exc())
388 Session.rollback()
389 Session.rollback()
389 raise
390 raise
390
391
391 @classmethod
392 @classmethod
392 def update(cls, users_group_id, form_data):
393 def update(cls, users_group_id, form_data):
393
394
394 try:
395 try:
395 users_group = cls.get(users_group_id, cache=False)
396 users_group = cls.get(users_group_id, cache=False)
396
397
397 for k, v in form_data.items():
398 for k, v in form_data.items():
398 if k == 'users_group_members':
399 if k == 'users_group_members':
399 users_group.members = []
400 users_group.members = []
400 Session.flush()
401 Session.flush()
401 members_list = []
402 members_list = []
402 if v:
403 if v:
403 for u_id in set(v):
404 for u_id in set(v):
404 members_list.append(UsersGroupMember(
405 members_list.append(UsersGroupMember(
405 users_group_id,
406 users_group_id,
406 u_id))
407 u_id))
407 setattr(users_group, 'members', members_list)
408 setattr(users_group, 'members', members_list)
408 setattr(users_group, k, v)
409 setattr(users_group, k, v)
409
410
410 Session.add(users_group)
411 Session.add(users_group)
411 Session.commit()
412 Session.commit()
412 except:
413 except:
413 log.error(traceback.format_exc())
414 log.error(traceback.format_exc())
414 Session.rollback()
415 Session.rollback()
415 raise
416 raise
416
417
417 @classmethod
418 @classmethod
418 def delete(cls, users_group_id):
419 def delete(cls, users_group_id):
419 try:
420 try:
420
421
421 # check if this group is not assigned to repo
422 # check if this group is not assigned to repo
422 assigned_groups = UsersGroupRepoToPerm.query()\
423 assigned_groups = UsersGroupRepoToPerm.query()\
423 .filter(UsersGroupRepoToPerm.users_group_id ==
424 .filter(UsersGroupRepoToPerm.users_group_id ==
424 users_group_id).all()
425 users_group_id).all()
425
426
426 if assigned_groups:
427 if assigned_groups:
427 raise UsersGroupsAssignedException('Group assigned to %s' %
428 raise UsersGroupsAssignedException('Group assigned to %s' %
428 assigned_groups)
429 assigned_groups)
429
430
430 users_group = cls.get(users_group_id, cache=False)
431 users_group = cls.get(users_group_id, cache=False)
431 Session.delete(users_group)
432 Session.delete(users_group)
432 Session.commit()
433 Session.commit()
433 except:
434 except:
434 log.error(traceback.format_exc())
435 log.error(traceback.format_exc())
435 Session.rollback()
436 Session.rollback()
436 raise
437 raise
437
438
438
439
439 class UsersGroupMember(Base, BaseModel):
440 class UsersGroupMember(Base, BaseModel):
440 __tablename__ = 'users_groups_members'
441 __tablename__ = 'users_groups_members'
441 __table_args__ = {'extend_existing':True}
442 __table_args__ = {'extend_existing':True}
442
443
443 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
444 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
444 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
445 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
445 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
446
447
447 user = relationship('User', lazy='joined')
448 user = relationship('User', lazy='joined')
448 users_group = relationship('UsersGroup')
449 users_group = relationship('UsersGroup')
449
450
450 def __init__(self, gr_id='', u_id=''):
451 def __init__(self, gr_id='', u_id=''):
451 self.users_group_id = gr_id
452 self.users_group_id = gr_id
452 self.user_id = u_id
453 self.user_id = u_id
453
454
454 class Repository(Base, BaseModel):
455 class Repository(Base, BaseModel):
455 __tablename__ = 'repositories'
456 __tablename__ = 'repositories'
456 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
457 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
457
458
458 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
459 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
459 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
460 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
460 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
461 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
461 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
462 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
462 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
463 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
463 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
464 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
464 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
465 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
465 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
466 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
466 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
467 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
467 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
468 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
468
469
469 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
470 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
470 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
471 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
471
472
472
473
473 user = relationship('User')
474 user = relationship('User')
474 fork = relationship('Repository', remote_side=repo_id)
475 fork = relationship('Repository', remote_side=repo_id)
475 group = relationship('Group')
476 group = relationship('Group')
476 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
477 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
477 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
478 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
478 stats = relationship('Statistics', cascade='all', uselist=False)
479 stats = relationship('Statistics', cascade='all', uselist=False)
479
480
480 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
481 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
481
482
482 logs = relationship('UserLog', cascade='all')
483 logs = relationship('UserLog', cascade='all')
483
484
484 def __repr__(self):
485 def __repr__(self):
485 return "<%s('%s:%s')>" % (self.__class__.__name__,
486 return "<%s('%s:%s')>" % (self.__class__.__name__,
486 self.repo_id, self.repo_name)
487 self.repo_id, self.repo_name)
487
488
488 @classmethod
489 @classmethod
489 def by_repo_name(cls, repo_name):
490 def by_repo_name(cls, repo_name):
490 q = Session.query(cls).filter(cls.repo_name == repo_name)
491 q = Session.query(cls).filter(cls.repo_name == repo_name)
491
492
492 q = q.options(joinedload(Repository.fork))\
493 q = q.options(joinedload(Repository.fork))\
493 .options(joinedload(Repository.user))\
494 .options(joinedload(Repository.user))\
494 .options(joinedload(Repository.group))\
495 .options(joinedload(Repository.group))\
495
496
496 return q.one()
497 return q.one()
497
498
498 @classmethod
499 @classmethod
499 def get_repo_forks(cls, repo_id):
500 def get_repo_forks(cls, repo_id):
500 return Session.query(cls).filter(Repository.fork_id == repo_id)
501 return Session.query(cls).filter(Repository.fork_id == repo_id)
501
502
502 @classmethod
503 @classmethod
503 def base_path(cls):
504 def base_path(cls):
504 """
505 """
505 Returns base path when all repos are stored
506 Returns base path when all repos are stored
506
507
507 :param cls:
508 :param cls:
508 """
509 """
509 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
510 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
510 q.options(FromCache("sql_cache_short", "repository_repo_path"))
511 q.options(FromCache("sql_cache_short", "repository_repo_path"))
511 return q.one().ui_value
512 return q.one().ui_value
512
513
513 @property
514 @property
514 def just_name(self):
515 def just_name(self):
515 return self.repo_name.split(os.sep)[-1]
516 return self.repo_name.split(os.sep)[-1]
516
517
517 @property
518 @property
518 def groups_with_parents(self):
519 def groups_with_parents(self):
519 groups = []
520 groups = []
520 if self.group is None:
521 if self.group is None:
521 return groups
522 return groups
522
523
523 cur_gr = self.group
524 cur_gr = self.group
524 groups.insert(0, cur_gr)
525 groups.insert(0, cur_gr)
525 while 1:
526 while 1:
526 gr = getattr(cur_gr, 'parent_group', None)
527 gr = getattr(cur_gr, 'parent_group', None)
527 cur_gr = cur_gr.parent_group
528 cur_gr = cur_gr.parent_group
528 if gr is None:
529 if gr is None:
529 break
530 break
530 groups.insert(0, gr)
531 groups.insert(0, gr)
531
532
532 return groups
533 return groups
533
534
534 @property
535 @property
535 def groups_and_repo(self):
536 def groups_and_repo(self):
536 return self.groups_with_parents, self.just_name
537 return self.groups_with_parents, self.just_name
537
538
538 @LazyProperty
539 @LazyProperty
539 def repo_path(self):
540 def repo_path(self):
540 """
541 """
541 Returns base full path for that repository means where it actually
542 Returns base full path for that repository means where it actually
542 exists on a filesystem
543 exists on a filesystem
543 """
544 """
544 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
545 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
545 q.options(FromCache("sql_cache_short", "repository_repo_path"))
546 q.options(FromCache("sql_cache_short", "repository_repo_path"))
546 return q.one().ui_value
547 return q.one().ui_value
547
548
548 @property
549 @property
549 def repo_full_path(self):
550 def repo_full_path(self):
550 p = [self.repo_path]
551 p = [self.repo_path]
551 # we need to split the name by / since this is how we store the
552 # we need to split the name by / since this is how we store the
552 # names in the database, but that eventually needs to be converted
553 # names in the database, but that eventually needs to be converted
553 # into a valid system path
554 # into a valid system path
554 p += self.repo_name.split('/')
555 p += self.repo_name.split('/')
555 return os.path.join(*p)
556 return os.path.join(*p)
556
557
557 @property
558 @property
558 def _ui(self):
559 def _ui(self):
559 """
560 """
560 Creates an db based ui object for this repository
561 Creates an db based ui object for this repository
561 """
562 """
562 from mercurial import ui
563 from mercurial import ui
563 from mercurial import config
564 from mercurial import config
564 baseui = ui.ui()
565 baseui = ui.ui()
565
566
566 #clean the baseui object
567 #clean the baseui object
567 baseui._ocfg = config.config()
568 baseui._ocfg = config.config()
568 baseui._ucfg = config.config()
569 baseui._ucfg = config.config()
569 baseui._tcfg = config.config()
570 baseui._tcfg = config.config()
570
571
571
572
572 ret = Session.query(RhodeCodeUi)\
573 ret = Session.query(RhodeCodeUi)\
573 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
574 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
574
575
575 hg_ui = ret
576 hg_ui = ret
576 for ui_ in hg_ui:
577 for ui_ in hg_ui:
577 if ui_.ui_active:
578 if ui_.ui_active:
578 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
579 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
579 ui_.ui_key, ui_.ui_value)
580 ui_.ui_key, ui_.ui_value)
580 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
581 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
581
582
582 return baseui
583 return baseui
583
584
584 @classmethod
585 @classmethod
585 def is_valid(cls, repo_name):
586 def is_valid(cls, repo_name):
586 """
587 """
587 returns True if given repo name is a valid filesystem repository
588 returns True if given repo name is a valid filesystem repository
588
589
589 @param cls:
590 @param cls:
590 @param repo_name:
591 @param repo_name:
591 """
592 """
592 from rhodecode.lib.utils import is_valid_repo
593 from rhodecode.lib.utils import is_valid_repo
593
594
594 return is_valid_repo(repo_name, cls.base_path())
595 return is_valid_repo(repo_name, cls.base_path())
595
596
596
597
597 #==========================================================================
598 #==========================================================================
598 # SCM PROPERTIES
599 # SCM PROPERTIES
599 #==========================================================================
600 #==========================================================================
600
601
601 def get_changeset(self, rev):
602 def get_changeset(self, rev):
602 return get_changeset_safe(self.scm_instance, rev)
603 return get_changeset_safe(self.scm_instance, rev)
603
604
604 @property
605 @property
605 def tip(self):
606 def tip(self):
606 return self.get_changeset('tip')
607 return self.get_changeset('tip')
607
608
608 @property
609 @property
609 def author(self):
610 def author(self):
610 return self.tip.author
611 return self.tip.author
611
612
612 @property
613 @property
613 def last_change(self):
614 def last_change(self):
614 return self.scm_instance.last_change
615 return self.scm_instance.last_change
615
616
616 #==========================================================================
617 #==========================================================================
617 # SCM CACHE INSTANCE
618 # SCM CACHE INSTANCE
618 #==========================================================================
619 #==========================================================================
619
620
620 @property
621 @property
621 def invalidate(self):
622 def invalidate(self):
622 """
623 """
623 Returns Invalidation object if this repo should be invalidated
624 Returns Invalidation object if this repo should be invalidated
624 None otherwise. `cache_active = False` means that this cache
625 None otherwise. `cache_active = False` means that this cache
625 state is not valid and needs to be invalidated
626 state is not valid and needs to be invalidated
626 """
627 """
627 return Session.query(CacheInvalidation)\
628 return Session.query(CacheInvalidation)\
628 .filter(CacheInvalidation.cache_key == self.repo_name)\
629 .filter(CacheInvalidation.cache_key == self.repo_name)\
629 .filter(CacheInvalidation.cache_active == False)\
630 .filter(CacheInvalidation.cache_active == False)\
630 .scalar()
631 .scalar()
631
632
632 def set_invalidate(self):
633 def set_invalidate(self):
633 """
634 """
634 set a cache for invalidation for this instance
635 set a cache for invalidation for this instance
635 """
636 """
636 inv = Session.query(CacheInvalidation)\
637 inv = Session.query(CacheInvalidation)\
637 .filter(CacheInvalidation.cache_key == self.repo_name)\
638 .filter(CacheInvalidation.cache_key == self.repo_name)\
638 .scalar()
639 .scalar()
639
640
640 if inv is None:
641 if inv is None:
641 inv = CacheInvalidation(self.repo_name)
642 inv = CacheInvalidation(self.repo_name)
642 inv.cache_active = True
643 inv.cache_active = True
643 Session.add(inv)
644 Session.add(inv)
644 Session.commit()
645 Session.commit()
645
646
646 @LazyProperty
647 @LazyProperty
647 def scm_instance(self):
648 def scm_instance(self):
648 return self.__get_instance()
649 return self.__get_instance()
649
650
650 @property
651 @property
651 def scm_instance_cached(self):
652 def scm_instance_cached(self):
652 @cache_region('long_term')
653 @cache_region('long_term')
653 def _c(repo_name):
654 def _c(repo_name):
654 return self.__get_instance()
655 return self.__get_instance()
655
656
656 # TODO: remove this trick when beaker 1.6 is released
657 # TODO: remove this trick when beaker 1.6 is released
657 # and have fixed this issue with not supporting unicode keys
658 # and have fixed this issue with not supporting unicode keys
658 rn = safe_str(self.repo_name)
659 rn = safe_str(self.repo_name)
659
660
660 inv = self.invalidate
661 inv = self.invalidate
661 if inv is not None:
662 if inv is not None:
662 region_invalidate(_c, None, rn)
663 region_invalidate(_c, None, rn)
663 # update our cache
664 # update our cache
664 inv.cache_active = True
665 inv.cache_active = True
665 Session.add(inv)
666 Session.add(inv)
666 Session.commit()
667 Session.commit()
667
668
668 return _c(rn)
669 return _c(rn)
669
670
670 def __get_instance(self):
671 def __get_instance(self):
671
672
672 repo_full_path = self.repo_full_path
673 repo_full_path = self.repo_full_path
673
674
674 try:
675 try:
675 alias = get_scm(repo_full_path)[0]
676 alias = get_scm(repo_full_path)[0]
676 log.debug('Creating instance of %s repository', alias)
677 log.debug('Creating instance of %s repository', alias)
677 backend = get_backend(alias)
678 backend = get_backend(alias)
678 except VCSError:
679 except VCSError:
679 log.error(traceback.format_exc())
680 log.error(traceback.format_exc())
680 log.error('Perhaps this repository is in db and not in '
681 log.error('Perhaps this repository is in db and not in '
681 'filesystem run rescan repositories with '
682 'filesystem run rescan repositories with '
682 '"destroy old data " option from admin panel')
683 '"destroy old data " option from admin panel')
683 return
684 return
684
685
685 if alias == 'hg':
686 if alias == 'hg':
686
687
687 repo = backend(safe_str(repo_full_path), create=False,
688 repo = backend(safe_str(repo_full_path), create=False,
688 baseui=self._ui)
689 baseui=self._ui)
689 #skip hidden web repository
690 #skip hidden web repository
690 if repo._get_hidden():
691 if repo._get_hidden():
691 return
692 return
692 else:
693 else:
693 repo = backend(repo_full_path, create=False)
694 repo = backend(repo_full_path, create=False)
694
695
695 return repo
696 return repo
696
697
697
698
698 class Group(Base, BaseModel):
699 class Group(Base, BaseModel):
699 __tablename__ = 'groups'
700 __tablename__ = 'groups'
700 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
701 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
701 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
702 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
702 __mapper_args__ = {'order_by':'group_name'}
703 __mapper_args__ = {'order_by':'group_name'}
703
704
704 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
705 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
705 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
706 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
706 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
707 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
707 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
708 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
708
709
709 parent_group = relationship('Group', remote_side=group_id)
710 parent_group = relationship('Group', remote_side=group_id)
710
711
711
712
712 def __init__(self, group_name='', parent_group=None):
713 def __init__(self, group_name='', parent_group=None):
713 self.group_name = group_name
714 self.group_name = group_name
714 self.parent_group = parent_group
715 self.parent_group = parent_group
715
716
716 def __repr__(self):
717 def __repr__(self):
717 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
718 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
718 self.group_name)
719 self.group_name)
719
720
720 @classmethod
721 @classmethod
721 def url_sep(cls):
722 def url_sep(cls):
722 return '/'
723 return '/'
723
724
724 @property
725 @property
725 def parents(self):
726 def parents(self):
726 parents_recursion_limit = 5
727 parents_recursion_limit = 5
727 groups = []
728 groups = []
728 if self.parent_group is None:
729 if self.parent_group is None:
729 return groups
730 return groups
730 cur_gr = self.parent_group
731 cur_gr = self.parent_group
731 groups.insert(0, cur_gr)
732 groups.insert(0, cur_gr)
732 cnt = 0
733 cnt = 0
733 while 1:
734 while 1:
734 cnt += 1
735 cnt += 1
735 gr = getattr(cur_gr, 'parent_group', None)
736 gr = getattr(cur_gr, 'parent_group', None)
736 cur_gr = cur_gr.parent_group
737 cur_gr = cur_gr.parent_group
737 if gr is None:
738 if gr is None:
738 break
739 break
739 if cnt == parents_recursion_limit:
740 if cnt == parents_recursion_limit:
740 # this will prevent accidental infinit loops
741 # this will prevent accidental infinit loops
741 log.error('group nested more than %s' %
742 log.error('group nested more than %s' %
742 parents_recursion_limit)
743 parents_recursion_limit)
743 break
744 break
744
745
745 groups.insert(0, gr)
746 groups.insert(0, gr)
746 return groups
747 return groups
747
748
748 @property
749 @property
749 def children(self):
750 def children(self):
750 return Session.query(Group).filter(Group.parent_group == self)
751 return Session.query(Group).filter(Group.parent_group == self)
751
752
752 @property
753 @property
753 def full_path(self):
754 def full_path(self):
754 return Group.url_sep().join([g.group_name for g in self.parents] +
755 return Group.url_sep().join([g.group_name for g in self.parents] +
755 [self.group_name])
756 [self.group_name])
756
757
757 @property
758 @property
758 def repositories(self):
759 def repositories(self):
759 return Session.query(Repository).filter(Repository.group == self)
760 return Session.query(Repository).filter(Repository.group == self)
760
761
761 @property
762 @property
762 def repositories_recursive_count(self):
763 def repositories_recursive_count(self):
763 cnt = self.repositories.count()
764 cnt = self.repositories.count()
764
765
765 def children_count(group):
766 def children_count(group):
766 cnt = 0
767 cnt = 0
767 for child in group.children:
768 for child in group.children:
768 cnt += child.repositories.count()
769 cnt += child.repositories.count()
769 cnt += children_count(child)
770 cnt += children_count(child)
770 return cnt
771 return cnt
771
772
772 return cnt + children_count(self)
773 return cnt + children_count(self)
773
774
774 class Permission(Base, BaseModel):
775 class Permission(Base, BaseModel):
775 __tablename__ = 'permissions'
776 __tablename__ = 'permissions'
776 __table_args__ = {'extend_existing':True}
777 __table_args__ = {'extend_existing':True}
777 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
778 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
778 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
779 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
779 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
780 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
780
781
781 def __repr__(self):
782 def __repr__(self):
782 return "<%s('%s:%s')>" % (self.__class__.__name__,
783 return "<%s('%s:%s')>" % (self.__class__.__name__,
783 self.permission_id, self.permission_name)
784 self.permission_id, self.permission_name)
784
785
785 @classmethod
786 @classmethod
786 def get_by_key(cls, key):
787 def get_by_key(cls, key):
787 return Session.query(cls).filter(cls.permission_name == key).scalar()
788 return Session.query(cls).filter(cls.permission_name == key).scalar()
788
789
789 class RepoToPerm(Base, BaseModel):
790 class RepoToPerm(Base, BaseModel):
790 __tablename__ = 'repo_to_perm'
791 __tablename__ = 'repo_to_perm'
791 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
792 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
792 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
793 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
793 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
794 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
794 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
795 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
795 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
796 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
796
797
797 user = relationship('User')
798 user = relationship('User')
798 permission = relationship('Permission')
799 permission = relationship('Permission')
799 repository = relationship('Repository')
800 repository = relationship('Repository')
800
801
801 class UserToPerm(Base, BaseModel):
802 class UserToPerm(Base, BaseModel):
802 __tablename__ = 'user_to_perm'
803 __tablename__ = 'user_to_perm'
803 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
804 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
804 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
805 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
805 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
806 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
806 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
807 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
807
808
808 user = relationship('User')
809 user = relationship('User')
809 permission = relationship('Permission')
810 permission = relationship('Permission')
810
811
811 @classmethod
812 @classmethod
812 def has_perm(cls, user_id, perm):
813 def has_perm(cls, user_id, perm):
813 if not isinstance(perm, Permission):
814 if not isinstance(perm, Permission):
814 raise Exception('perm needs to be an instance of Permission class')
815 raise Exception('perm needs to be an instance of Permission class')
815
816
816 return Session.query(cls).filter(cls.user_id == user_id)\
817 return Session.query(cls).filter(cls.user_id == user_id)\
817 .filter(cls.permission == perm).scalar() is not None
818 .filter(cls.permission == perm).scalar() is not None
818
819
819 @classmethod
820 @classmethod
820 def grant_perm(cls, user_id, perm):
821 def grant_perm(cls, user_id, perm):
821 if not isinstance(perm, Permission):
822 if not isinstance(perm, Permission):
822 raise Exception('perm needs to be an instance of Permission class')
823 raise Exception('perm needs to be an instance of Permission class')
823
824
824 new = cls()
825 new = cls()
825 new.user_id = user_id
826 new.user_id = user_id
826 new.permission = perm
827 new.permission = perm
827 try:
828 try:
828 Session.add(new)
829 Session.add(new)
829 Session.commit()
830 Session.commit()
830 except:
831 except:
831 Session.rollback()
832 Session.rollback()
832
833
833
834
834 @classmethod
835 @classmethod
835 def revoke_perm(cls, user_id, perm):
836 def revoke_perm(cls, user_id, perm):
836 if not isinstance(perm, Permission):
837 if not isinstance(perm, Permission):
837 raise Exception('perm needs to be an instance of Permission class')
838 raise Exception('perm needs to be an instance of Permission class')
838
839
839 try:
840 try:
840 Session.query(cls).filter(cls.user_id == user_id)\
841 Session.query(cls).filter(cls.user_id == user_id)\
841 .filter(cls.permission == perm).delete()
842 .filter(cls.permission == perm).delete()
842 Session.commit()
843 Session.commit()
843 except:
844 except:
844 Session.rollback()
845 Session.rollback()
845
846
846 class UsersGroupRepoToPerm(Base, BaseModel):
847 class UsersGroupRepoToPerm(Base, BaseModel):
847 __tablename__ = 'users_group_repo_to_perm'
848 __tablename__ = 'users_group_repo_to_perm'
848 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
849 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
849 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
850 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
850 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
851 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
851 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
852 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
852 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
853 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
853
854
854 users_group = relationship('UsersGroup')
855 users_group = relationship('UsersGroup')
855 permission = relationship('Permission')
856 permission = relationship('Permission')
856 repository = relationship('Repository')
857 repository = relationship('Repository')
857
858
858 def __repr__(self):
859 def __repr__(self):
859 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
860 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
860
861
861 class UsersGroupToPerm(Base, BaseModel):
862 class UsersGroupToPerm(Base, BaseModel):
862 __tablename__ = 'users_group_to_perm'
863 __tablename__ = 'users_group_to_perm'
863 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
864 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
864 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
865 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
865 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
866 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
866
867
867 users_group = relationship('UsersGroup')
868 users_group = relationship('UsersGroup')
868 permission = relationship('Permission')
869 permission = relationship('Permission')
869
870
870
871
871 @classmethod
872 @classmethod
872 def has_perm(cls, users_group_id, perm):
873 def has_perm(cls, users_group_id, perm):
873 if not isinstance(perm, Permission):
874 if not isinstance(perm, Permission):
874 raise Exception('perm needs to be an instance of Permission class')
875 raise Exception('perm needs to be an instance of Permission class')
875
876
876 return Session.query(cls).filter(cls.users_group_id ==
877 return Session.query(cls).filter(cls.users_group_id ==
877 users_group_id)\
878 users_group_id)\
878 .filter(cls.permission == perm)\
879 .filter(cls.permission == perm)\
879 .scalar() is not None
880 .scalar() is not None
880
881
881 @classmethod
882 @classmethod
882 def grant_perm(cls, users_group_id, perm):
883 def grant_perm(cls, users_group_id, perm):
883 if not isinstance(perm, Permission):
884 if not isinstance(perm, Permission):
884 raise Exception('perm needs to be an instance of Permission class')
885 raise Exception('perm needs to be an instance of Permission class')
885
886
886 new = cls()
887 new = cls()
887 new.users_group_id = users_group_id
888 new.users_group_id = users_group_id
888 new.permission = perm
889 new.permission = perm
889 try:
890 try:
890 Session.add(new)
891 Session.add(new)
891 Session.commit()
892 Session.commit()
892 except:
893 except:
893 Session.rollback()
894 Session.rollback()
894
895
895
896
896 @classmethod
897 @classmethod
897 def revoke_perm(cls, users_group_id, perm):
898 def revoke_perm(cls, users_group_id, perm):
898 if not isinstance(perm, Permission):
899 if not isinstance(perm, Permission):
899 raise Exception('perm needs to be an instance of Permission class')
900 raise Exception('perm needs to be an instance of Permission class')
900
901
901 try:
902 try:
902 Session.query(cls).filter(cls.users_group_id == users_group_id)\
903 Session.query(cls).filter(cls.users_group_id == users_group_id)\
903 .filter(cls.permission == perm).delete()
904 .filter(cls.permission == perm).delete()
904 Session.commit()
905 Session.commit()
905 except:
906 except:
906 Session.rollback()
907 Session.rollback()
907
908
908
909
909 class GroupToPerm(Base, BaseModel):
910 class GroupToPerm(Base, BaseModel):
910 __tablename__ = 'group_to_perm'
911 __tablename__ = 'group_to_perm'
911 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
912 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
912
913
913 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
914 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
914 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
915 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
915 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
916 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
916 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
917 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
917
918
918 user = relationship('User')
919 user = relationship('User')
919 permission = relationship('Permission')
920 permission = relationship('Permission')
920 group = relationship('Group')
921 group = relationship('Group')
921
922
922 class Statistics(Base, BaseModel):
923 class Statistics(Base, BaseModel):
923 __tablename__ = 'statistics'
924 __tablename__ = 'statistics'
924 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
925 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
925 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
926 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
926 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
927 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
927 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
928 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
928 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
929 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
929 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
930 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
930 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
931 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
931
932
932 repository = relationship('Repository', single_parent=True)
933 repository = relationship('Repository', single_parent=True)
933
934
934 class UserFollowing(Base, BaseModel):
935 class UserFollowing(Base, BaseModel):
935 __tablename__ = 'user_followings'
936 __tablename__ = 'user_followings'
936 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
937 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
937 UniqueConstraint('user_id', 'follows_user_id')
938 UniqueConstraint('user_id', 'follows_user_id')
938 , {'extend_existing':True})
939 , {'extend_existing':True})
939
940
940 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
941 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
942 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
943 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
943 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
944 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
944 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
945 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
945
946
946 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
947 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
947
948
948 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
949 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
949 follows_repository = relationship('Repository', order_by='Repository.repo_name')
950 follows_repository = relationship('Repository', order_by='Repository.repo_name')
950
951
951
952
952 @classmethod
953 @classmethod
953 def get_repo_followers(cls, repo_id):
954 def get_repo_followers(cls, repo_id):
954 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
955 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
955
956
956 class CacheInvalidation(Base, BaseModel):
957 class CacheInvalidation(Base, BaseModel):
957 __tablename__ = 'cache_invalidation'
958 __tablename__ = 'cache_invalidation'
958 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
959 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
959 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
960 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
960 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
961 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
961 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
962 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
962 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
963 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
963
964
964
965
965 def __init__(self, cache_key, cache_args=''):
966 def __init__(self, cache_key, cache_args=''):
966 self.cache_key = cache_key
967 self.cache_key = cache_key
967 self.cache_args = cache_args
968 self.cache_args = cache_args
968 self.cache_active = False
969 self.cache_active = False
969
970
970 def __repr__(self):
971 def __repr__(self):
971 return "<%s('%s:%s')>" % (self.__class__.__name__,
972 return "<%s('%s:%s')>" % (self.__class__.__name__,
972 self.cache_id, self.cache_key)
973 self.cache_id, self.cache_key)
973
974
974 class DbMigrateVersion(Base, BaseModel):
975 class DbMigrateVersion(Base, BaseModel):
975 __tablename__ = 'db_migrate_version'
976 __tablename__ = 'db_migrate_version'
976 __table_args__ = {'extend_existing':True}
977 __table_args__ = {'extend_existing':True}
977 repository_id = Column('repository_id', String(250), primary_key=True)
978 repository_id = Column('repository_id', String(250), primary_key=True)
978 repository_path = Column('repository_path', Text)
979 repository_path = Column('repository_path', Text)
979 version = Column('version', Integer)
980 version = Column('version', Integer)
@@ -1,668 +1,681 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import os
22 import os
23 import re
23 import re
24 import logging
24 import logging
25 import traceback
25 import traceback
26
26
27 import formencode
27 import formencode
28 from formencode import All
28 from formencode import All
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 Email, Bool, StringBoolean, Set
30 Email, Bool, StringBoolean, Set
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34
34
35 from rhodecode.lib.utils import repo_name_slug
35 from rhodecode.lib.utils import repo_name_slug
36 from rhodecode.lib.auth import authenticate, get_crypt_password
36 from rhodecode.lib.auth import authenticate, get_crypt_password
37 from rhodecode.lib.exceptions import LdapImportError
37 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.db import User, UsersGroup, Group
40 from rhodecode.model.db import User, UsersGroup, Group
41 from rhodecode import BACKENDS
41 from rhodecode import BACKENDS
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45 #this is needed to translate the messages using _() in validators
45 #this is needed to translate the messages using _() in validators
46 class State_obj(object):
46 class State_obj(object):
47 _ = staticmethod(_)
47 _ = staticmethod(_)
48
48
49 #==============================================================================
49 #==============================================================================
50 # VALIDATORS
50 # VALIDATORS
51 #==============================================================================
51 #==============================================================================
52 class ValidAuthToken(formencode.validators.FancyValidator):
52 class ValidAuthToken(formencode.validators.FancyValidator):
53 messages = {'invalid_token':_('Token mismatch')}
53 messages = {'invalid_token':_('Token mismatch')}
54
54
55 def validate_python(self, value, state):
55 def validate_python(self, value, state):
56
56
57 if value != authentication_token():
57 if value != authentication_token():
58 raise formencode.Invalid(self.message('invalid_token', state,
58 raise formencode.Invalid(self.message('invalid_token', state,
59 search_number=value), value, state)
59 search_number=value), value, state)
60
60
61 def ValidUsername(edit, old_data):
61 def ValidUsername(edit, old_data):
62 class _ValidUsername(formencode.validators.FancyValidator):
62 class _ValidUsername(formencode.validators.FancyValidator):
63
63
64 def validate_python(self, value, state):
64 def validate_python(self, value, state):
65 if value in ['default', 'new_user']:
65 if value in ['default', 'new_user']:
66 raise formencode.Invalid(_('Invalid username'), value, state)
66 raise formencode.Invalid(_('Invalid username'), value, state)
67 #check if user is unique
67 #check if user is unique
68 old_un = None
68 old_un = None
69 if edit:
69 if edit:
70 old_un = UserModel().get(old_data.get('user_id')).username
70 old_un = UserModel().get(old_data.get('user_id')).username
71
71
72 if old_un != value or not edit:
72 if old_un != value or not edit:
73 if UserModel().get_by_username(value, cache=False,
73 if UserModel().get_by_username(value, cache=False,
74 case_insensitive=True):
74 case_insensitive=True):
75 raise formencode.Invalid(_('This username already '
75 raise formencode.Invalid(_('This username already '
76 'exists') , value, state)
76 'exists') , value, state)
77
77
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 raise formencode.Invalid(_('Username may only contain '
79 raise formencode.Invalid(_('Username may only contain '
80 'alphanumeric characters '
80 'alphanumeric characters '
81 'underscores, periods or dashes '
81 'underscores, periods or dashes '
82 'and must begin with alphanumeric '
82 'and must begin with alphanumeric '
83 'character'), value, state)
83 'character'), value, state)
84
84
85 return _ValidUsername
85 return _ValidUsername
86
86
87
87
88 def ValidUsersGroup(edit, old_data):
88 def ValidUsersGroup(edit, old_data):
89
89
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
91
91
92 def validate_python(self, value, state):
92 def validate_python(self, value, state):
93 if value in ['default']:
93 if value in ['default']:
94 raise formencode.Invalid(_('Invalid group name'), value, state)
94 raise formencode.Invalid(_('Invalid group name'), value, state)
95 #check if group is unique
95 #check if group is unique
96 old_ugname = None
96 old_ugname = None
97 if edit:
97 if edit:
98 old_ugname = UsersGroup.get(
98 old_ugname = UsersGroup.get(
99 old_data.get('users_group_id')).users_group_name
99 old_data.get('users_group_id')).users_group_name
100
100
101 if old_ugname != value or not edit:
101 if old_ugname != value or not edit:
102 if UsersGroup.get_by_group_name(value, cache=False,
102 if UsersGroup.get_by_group_name(value, cache=False,
103 case_insensitive=True):
103 case_insensitive=True):
104 raise formencode.Invalid(_('This users group '
104 raise formencode.Invalid(_('This users group '
105 'already exists') , value,
105 'already exists') , value,
106 state)
106 state)
107
107
108
108
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 raise formencode.Invalid(_('Group name may only contain '
110 raise formencode.Invalid(_('Group name may only contain '
111 'alphanumeric characters '
111 'alphanumeric characters '
112 'underscores, periods or dashes '
112 'underscores, periods or dashes '
113 'and must begin with alphanumeric '
113 'and must begin with alphanumeric '
114 'character'), value, state)
114 'character'), value, state)
115
115
116 return _ValidUsersGroup
116 return _ValidUsersGroup
117
117
118
118
119 def ValidReposGroup(edit, old_data):
119 def ValidReposGroup(edit, old_data):
120 class _ValidReposGroup(formencode.validators.FancyValidator):
120 class _ValidReposGroup(formencode.validators.FancyValidator):
121
121
122 def validate_python(self, value, state):
122 def validate_python(self, value, state):
123 #TODO WRITE VALIDATIONS
123 #TODO WRITE VALIDATIONS
124 group_name = value.get('group_name')
124 group_name = value.get('group_name')
125 group_parent_id = int(value.get('group_parent_id') or - 1)
125 group_parent_id = int(value.get('group_parent_id') or - 1)
126
126
127 # slugify repo group just in case :)
127 # slugify repo group just in case :)
128 slug = repo_name_slug(group_name)
128 slug = repo_name_slug(group_name)
129
129
130 # check for parent of self
130 # check for parent of self
131 if edit and old_data['group_id'] == group_parent_id:
131 if edit and old_data['group_id'] == group_parent_id:
132 e_dict = {'group_parent_id':_('Cannot assign this group '
132 e_dict = {'group_parent_id':_('Cannot assign this group '
133 'as parent')}
133 'as parent')}
134 raise formencode.Invalid('', value, state,
134 raise formencode.Invalid('', value, state,
135 error_dict=e_dict)
135 error_dict=e_dict)
136
136
137 old_gname = None
137 old_gname = None
138 if edit:
138 if edit:
139 old_gname = Group.get(
139 old_gname = Group.get(
140 old_data.get('group_id')).group_name
140 old_data.get('group_id')).group_name
141
141
142 if old_gname != group_name or not edit:
142 if old_gname != group_name or not edit:
143 # check filesystem
143 # check filesystem
144 gr = Group.query().filter(Group.group_name == slug)\
144 gr = Group.query().filter(Group.group_name == slug)\
145 .filter(Group.group_parent_id == group_parent_id).scalar()
145 .filter(Group.group_parent_id == group_parent_id).scalar()
146
146
147 if gr:
147 if gr:
148 e_dict = {'group_name':_('This group already exists')}
148 e_dict = {'group_name':_('This group already exists')}
149 raise formencode.Invalid('', value, state,
149 raise formencode.Invalid('', value, state,
150 error_dict=e_dict)
150 error_dict=e_dict)
151
151
152 return _ValidReposGroup
152 return _ValidReposGroup
153
153
154 class ValidPassword(formencode.validators.FancyValidator):
154 class ValidPassword(formencode.validators.FancyValidator):
155
155
156 def to_python(self, value, state):
156 def to_python(self, value, state):
157
157
158 if value:
158 if value:
159
159
160 if value.get('password'):
160 if value.get('password'):
161 try:
161 try:
162 value['password'] = get_crypt_password(value['password'])
162 value['password'] = get_crypt_password(value['password'])
163 except UnicodeEncodeError:
163 except UnicodeEncodeError:
164 e_dict = {'password':_('Invalid characters in password')}
164 e_dict = {'password':_('Invalid characters in password')}
165 raise formencode.Invalid('', value, state, error_dict=e_dict)
165 raise formencode.Invalid('', value, state, error_dict=e_dict)
166
166
167 if value.get('password_confirmation'):
167 if value.get('password_confirmation'):
168 try:
168 try:
169 value['password_confirmation'] = \
169 value['password_confirmation'] = \
170 get_crypt_password(value['password_confirmation'])
170 get_crypt_password(value['password_confirmation'])
171 except UnicodeEncodeError:
171 except UnicodeEncodeError:
172 e_dict = {'password_confirmation':_('Invalid characters in password')}
172 e_dict = {'password_confirmation':_('Invalid characters in password')}
173 raise formencode.Invalid('', value, state, error_dict=e_dict)
173 raise formencode.Invalid('', value, state, error_dict=e_dict)
174
174
175 if value.get('new_password'):
175 if value.get('new_password'):
176 try:
176 try:
177 value['new_password'] = \
177 value['new_password'] = \
178 get_crypt_password(value['new_password'])
178 get_crypt_password(value['new_password'])
179 except UnicodeEncodeError:
179 except UnicodeEncodeError:
180 e_dict = {'new_password':_('Invalid characters in password')}
180 e_dict = {'new_password':_('Invalid characters in password')}
181 raise formencode.Invalid('', value, state, error_dict=e_dict)
181 raise formencode.Invalid('', value, state, error_dict=e_dict)
182
182
183 return value
183 return value
184
184
185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
186
186
187 def validate_python(self, value, state):
187 def validate_python(self, value, state):
188
188
189 if value['password'] != value['password_confirmation']:
189 if value['password'] != value['password_confirmation']:
190 e_dict = {'password_confirmation':
190 e_dict = {'password_confirmation':
191 _('Passwords do not match')}
191 _('Passwords do not match')}
192 raise formencode.Invalid('', value, state, error_dict=e_dict)
192 raise formencode.Invalid('', value, state, error_dict=e_dict)
193
193
194 class ValidAuth(formencode.validators.FancyValidator):
194 class ValidAuth(formencode.validators.FancyValidator):
195 messages = {
195 messages = {
196 'invalid_password':_('invalid password'),
196 'invalid_password':_('invalid password'),
197 'invalid_login':_('invalid user name'),
197 'invalid_login':_('invalid user name'),
198 'disabled_account':_('Your account is disabled')
198 'disabled_account':_('Your account is disabled')
199
199
200 }
200 }
201 #error mapping
201 #error mapping
202 e_dict = {'username':messages['invalid_login'],
202 e_dict = {'username':messages['invalid_login'],
203 'password':messages['invalid_password']}
203 'password':messages['invalid_password']}
204 e_dict_disable = {'username':messages['disabled_account']}
204 e_dict_disable = {'username':messages['disabled_account']}
205
205
206 def validate_python(self, value, state):
206 def validate_python(self, value, state):
207 password = value['password']
207 password = value['password']
208 username = value['username']
208 username = value['username']
209 user = UserModel().get_by_username(username)
209 user = UserModel().get_by_username(username)
210
210
211 if authenticate(username, password):
211 if authenticate(username, password):
212 return value
212 return value
213 else:
213 else:
214 if user and user.active is False:
214 if user and user.active is False:
215 log.warning('user %s is disabled', username)
215 log.warning('user %s is disabled', username)
216 raise formencode.Invalid(self.message('disabled_account',
216 raise formencode.Invalid(self.message('disabled_account',
217 state=State_obj),
217 state=State_obj),
218 value, state,
218 value, state,
219 error_dict=self.e_dict_disable)
219 error_dict=self.e_dict_disable)
220 else:
220 else:
221 log.warning('user %s not authenticated', username)
221 log.warning('user %s not authenticated', username)
222 raise formencode.Invalid(self.message('invalid_password',
222 raise formencode.Invalid(self.message('invalid_password',
223 state=State_obj), value, state,
223 state=State_obj), value, state,
224 error_dict=self.e_dict)
224 error_dict=self.e_dict)
225
225
226 class ValidRepoUser(formencode.validators.FancyValidator):
226 class ValidRepoUser(formencode.validators.FancyValidator):
227
227
228 def to_python(self, value, state):
228 def to_python(self, value, state):
229 try:
229 try:
230 User.query().filter(User.active == True)\
230 User.query().filter(User.active == True)\
231 .filter(User.username == value).one()
231 .filter(User.username == value).one()
232 except Exception:
232 except Exception:
233 raise formencode.Invalid(_('This username is not valid'),
233 raise formencode.Invalid(_('This username is not valid'),
234 value, state)
234 value, state)
235 return value
235 return value
236
236
237 def ValidRepoName(edit, old_data):
237 def ValidRepoName(edit, old_data):
238 class _ValidRepoName(formencode.validators.FancyValidator):
238 class _ValidRepoName(formencode.validators.FancyValidator):
239 def to_python(self, value, state):
239 def to_python(self, value, state):
240
240
241 repo_name = value.get('repo_name')
241 repo_name = value.get('repo_name')
242
242
243 slug = repo_name_slug(repo_name)
243 slug = repo_name_slug(repo_name)
244 if slug in ['_admin', '']:
244 if slug in ['_admin', '']:
245 e_dict = {'repo_name': _('This repository name is disallowed')}
245 e_dict = {'repo_name': _('This repository name is disallowed')}
246 raise formencode.Invalid('', value, state, error_dict=e_dict)
246 raise formencode.Invalid('', value, state, error_dict=e_dict)
247
247
248
248
249 if value.get('repo_group'):
249 if value.get('repo_group'):
250 gr = Group.get(value.get('repo_group'))
250 gr = Group.get(value.get('repo_group'))
251 group_path = gr.full_path
251 group_path = gr.full_path
252 # value needs to be aware of group name in order to check
252 # value needs to be aware of group name in order to check
253 # db key This is an actuall just the name to store in the
253 # db key This is an actuall just the name to store in the
254 # database
254 # database
255 repo_name_full = group_path + Group.url_sep() + repo_name
255 repo_name_full = group_path + Group.url_sep() + repo_name
256 else:
256 else:
257 group_path = ''
257 group_path = ''
258 repo_name_full = repo_name
258 repo_name_full = repo_name
259
259
260
260
261 value['repo_name_full'] = repo_name_full
261 value['repo_name_full'] = repo_name_full
262 if old_data.get('repo_name') != repo_name_full or not edit:
262 if old_data.get('repo_name') != repo_name_full or not edit:
263
263
264 if group_path != '':
264 if group_path != '':
265 if RepoModel().get_by_repo_name(repo_name_full,):
265 if RepoModel().get_by_repo_name(repo_name_full,):
266 e_dict = {'repo_name':_('This repository already '
266 e_dict = {'repo_name':_('This repository already '
267 'exists in group "%s"') %
267 'exists in group "%s"') %
268 gr.group_name}
268 gr.group_name}
269 raise formencode.Invalid('', value, state,
269 raise formencode.Invalid('', value, state,
270 error_dict=e_dict)
270 error_dict=e_dict)
271
271
272 else:
272 else:
273 if RepoModel().get_by_repo_name(repo_name_full):
273 if RepoModel().get_by_repo_name(repo_name_full):
274 e_dict = {'repo_name':_('This repository '
274 e_dict = {'repo_name':_('This repository '
275 'already exists')}
275 'already exists')}
276 raise formencode.Invalid('', value, state,
276 raise formencode.Invalid('', value, state,
277 error_dict=e_dict)
277 error_dict=e_dict)
278 return value
278 return value
279
279
280
280
281 return _ValidRepoName
281 return _ValidRepoName
282
282
283 def ValidForkName():
283 def ValidForkName():
284 class _ValidForkName(formencode.validators.FancyValidator):
284 class _ValidForkName(formencode.validators.FancyValidator):
285 def to_python(self, value, state):
285 def to_python(self, value, state):
286
287 repo_name = value.get('fork_name')
288
289 slug = repo_name_slug(repo_name)
290 if slug in ['_admin', '']:
291 e_dict = {'repo_name': _('This repository name is disallowed')}
292 raise formencode.Invalid('', value, state, error_dict=e_dict)
293
294 if RepoModel().get_by_repo_name(repo_name):
295 e_dict = {'fork_name':_('This repository '
296 'already exists')}
297 raise formencode.Invalid('', value, state,
298 error_dict=e_dict)
286 return value
299 return value
287 return _ValidForkName
300 return _ValidForkName
288
301
289
302
290 def SlugifyName():
303 def SlugifyName():
291 class _SlugifyName(formencode.validators.FancyValidator):
304 class _SlugifyName(formencode.validators.FancyValidator):
292
305
293 def to_python(self, value, state):
306 def to_python(self, value, state):
294 return repo_name_slug(value)
307 return repo_name_slug(value)
295
308
296 return _SlugifyName
309 return _SlugifyName
297
310
298 def ValidCloneUri():
311 def ValidCloneUri():
299 from mercurial.httprepo import httprepository, httpsrepository
312 from mercurial.httprepo import httprepository, httpsrepository
300 from rhodecode.lib.utils import make_ui
313 from rhodecode.lib.utils import make_ui
301
314
302 class _ValidCloneUri(formencode.validators.FancyValidator):
315 class _ValidCloneUri(formencode.validators.FancyValidator):
303
316
304 def to_python(self, value, state):
317 def to_python(self, value, state):
305 if not value:
318 if not value:
306 pass
319 pass
307 elif value.startswith('https'):
320 elif value.startswith('https'):
308 try:
321 try:
309 httpsrepository(make_ui('db'), value).capabilities
322 httpsrepository(make_ui('db'), value).capabilities
310 except Exception, e:
323 except Exception, e:
311 log.error(traceback.format_exc())
324 log.error(traceback.format_exc())
312 raise formencode.Invalid(_('invalid clone url'), value,
325 raise formencode.Invalid(_('invalid clone url'), value,
313 state)
326 state)
314 elif value.startswith('http'):
327 elif value.startswith('http'):
315 try:
328 try:
316 httprepository(make_ui('db'), value).capabilities
329 httprepository(make_ui('db'), value).capabilities
317 except Exception, e:
330 except Exception, e:
318 log.error(traceback.format_exc())
331 log.error(traceback.format_exc())
319 raise formencode.Invalid(_('invalid clone url'), value,
332 raise formencode.Invalid(_('invalid clone url'), value,
320 state)
333 state)
321 else:
334 else:
322 raise formencode.Invalid(_('Invalid clone url, provide a '
335 raise formencode.Invalid(_('Invalid clone url, provide a '
323 'valid clone http\s url'), value,
336 'valid clone http\s url'), value,
324 state)
337 state)
325 return value
338 return value
326
339
327 return _ValidCloneUri
340 return _ValidCloneUri
328
341
329 def ValidForkType(old_data):
342 def ValidForkType(old_data):
330 class _ValidForkType(formencode.validators.FancyValidator):
343 class _ValidForkType(formencode.validators.FancyValidator):
331
344
332 def to_python(self, value, state):
345 def to_python(self, value, state):
333 if old_data['repo_type'] != value:
346 if old_data['repo_type'] != value:
334 raise formencode.Invalid(_('Fork have to be the same '
347 raise formencode.Invalid(_('Fork have to be the same '
335 'type as original'), value, state)
348 'type as original'), value, state)
336
349
337 return value
350 return value
338 return _ValidForkType
351 return _ValidForkType
339
352
340 class ValidPerms(formencode.validators.FancyValidator):
353 class ValidPerms(formencode.validators.FancyValidator):
341 messages = {'perm_new_member_name':_('This username or users group name'
354 messages = {'perm_new_member_name':_('This username or users group name'
342 ' is not valid')}
355 ' is not valid')}
343
356
344 def to_python(self, value, state):
357 def to_python(self, value, state):
345 perms_update = []
358 perms_update = []
346 perms_new = []
359 perms_new = []
347 #build a list of permission to update and new permission to create
360 #build a list of permission to update and new permission to create
348 for k, v in value.items():
361 for k, v in value.items():
349 #means new added member to permissions
362 #means new added member to permissions
350 if k.startswith('perm_new_member'):
363 if k.startswith('perm_new_member'):
351 new_perm = value.get('perm_new_member', False)
364 new_perm = value.get('perm_new_member', False)
352 new_member = value.get('perm_new_member_name', False)
365 new_member = value.get('perm_new_member_name', False)
353 new_type = value.get('perm_new_member_type')
366 new_type = value.get('perm_new_member_type')
354
367
355 if new_member and new_perm:
368 if new_member and new_perm:
356 if (new_member, new_perm, new_type) not in perms_new:
369 if (new_member, new_perm, new_type) not in perms_new:
357 perms_new.append((new_member, new_perm, new_type))
370 perms_new.append((new_member, new_perm, new_type))
358 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
371 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
359 member = k[7:]
372 member = k[7:]
360 t = {'u':'user',
373 t = {'u':'user',
361 'g':'users_group'}[k[0]]
374 'g':'users_group'}[k[0]]
362 if member == 'default':
375 if member == 'default':
363 if value['private']:
376 if value['private']:
364 #set none for default when updating to private repo
377 #set none for default when updating to private repo
365 v = 'repository.none'
378 v = 'repository.none'
366 perms_update.append((member, v, t))
379 perms_update.append((member, v, t))
367
380
368 value['perms_updates'] = perms_update
381 value['perms_updates'] = perms_update
369 value['perms_new'] = perms_new
382 value['perms_new'] = perms_new
370
383
371 #update permissions
384 #update permissions
372 for k, v, t in perms_new:
385 for k, v, t in perms_new:
373 try:
386 try:
374 if t is 'user':
387 if t is 'user':
375 self.user_db = User.query()\
388 self.user_db = User.query()\
376 .filter(User.active == True)\
389 .filter(User.active == True)\
377 .filter(User.username == k).one()
390 .filter(User.username == k).one()
378 if t is 'users_group':
391 if t is 'users_group':
379 self.user_db = UsersGroup.query()\
392 self.user_db = UsersGroup.query()\
380 .filter(UsersGroup.users_group_active == True)\
393 .filter(UsersGroup.users_group_active == True)\
381 .filter(UsersGroup.users_group_name == k).one()
394 .filter(UsersGroup.users_group_name == k).one()
382
395
383 except Exception:
396 except Exception:
384 msg = self.message('perm_new_member_name',
397 msg = self.message('perm_new_member_name',
385 state=State_obj)
398 state=State_obj)
386 raise formencode.Invalid(msg, value, state,
399 raise formencode.Invalid(msg, value, state,
387 error_dict={'perm_new_member_name':msg})
400 error_dict={'perm_new_member_name':msg})
388 return value
401 return value
389
402
390 class ValidSettings(formencode.validators.FancyValidator):
403 class ValidSettings(formencode.validators.FancyValidator):
391
404
392 def to_python(self, value, state):
405 def to_python(self, value, state):
393 #settings form can't edit user
406 #settings form can't edit user
394 if value.has_key('user'):
407 if value.has_key('user'):
395 del['value']['user']
408 del['value']['user']
396
409
397 return value
410 return value
398
411
399 class ValidPath(formencode.validators.FancyValidator):
412 class ValidPath(formencode.validators.FancyValidator):
400 def to_python(self, value, state):
413 def to_python(self, value, state):
401
414
402 if not os.path.isdir(value):
415 if not os.path.isdir(value):
403 msg = _('This is not a valid path')
416 msg = _('This is not a valid path')
404 raise formencode.Invalid(msg, value, state,
417 raise formencode.Invalid(msg, value, state,
405 error_dict={'paths_root_path':msg})
418 error_dict={'paths_root_path':msg})
406 return value
419 return value
407
420
408 def UniqSystemEmail(old_data):
421 def UniqSystemEmail(old_data):
409 class _UniqSystemEmail(formencode.validators.FancyValidator):
422 class _UniqSystemEmail(formencode.validators.FancyValidator):
410 def to_python(self, value, state):
423 def to_python(self, value, state):
411 value = value.lower()
424 value = value.lower()
412 if old_data.get('email') != value:
425 if old_data.get('email') != value:
413 user = User.query().filter(User.email == value).scalar()
426 user = User.query().filter(User.email == value).scalar()
414 if user:
427 if user:
415 raise formencode.Invalid(
428 raise formencode.Invalid(
416 _("This e-mail address is already taken"),
429 _("This e-mail address is already taken"),
417 value, state)
430 value, state)
418 return value
431 return value
419
432
420 return _UniqSystemEmail
433 return _UniqSystemEmail
421
434
422 class ValidSystemEmail(formencode.validators.FancyValidator):
435 class ValidSystemEmail(formencode.validators.FancyValidator):
423 def to_python(self, value, state):
436 def to_python(self, value, state):
424 value = value.lower()
437 value = value.lower()
425 user = User.query().filter(User.email == value).scalar()
438 user = User.query().filter(User.email == value).scalar()
426 if user is None:
439 if user is None:
427 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
440 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
428 value, state)
441 value, state)
429
442
430 return value
443 return value
431
444
432 class LdapLibValidator(formencode.validators.FancyValidator):
445 class LdapLibValidator(formencode.validators.FancyValidator):
433
446
434 def to_python(self, value, state):
447 def to_python(self, value, state):
435
448
436 try:
449 try:
437 import ldap
450 import ldap
438 except ImportError:
451 except ImportError:
439 raise LdapImportError
452 raise LdapImportError
440 return value
453 return value
441
454
442 class AttrLoginValidator(formencode.validators.FancyValidator):
455 class AttrLoginValidator(formencode.validators.FancyValidator):
443
456
444 def to_python(self, value, state):
457 def to_python(self, value, state):
445
458
446 if not value or not isinstance(value, (str, unicode)):
459 if not value or not isinstance(value, (str, unicode)):
447 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
460 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
448 "must be specified - this is the name "
461 "must be specified - this is the name "
449 "of the attribute that is equivalent "
462 "of the attribute that is equivalent "
450 "to 'username'"),
463 "to 'username'"),
451 value, state)
464 value, state)
452
465
453 return value
466 return value
454
467
455 #===============================================================================
468 #===============================================================================
456 # FORMS
469 # FORMS
457 #===============================================================================
470 #===============================================================================
458 class LoginForm(formencode.Schema):
471 class LoginForm(formencode.Schema):
459 allow_extra_fields = True
472 allow_extra_fields = True
460 filter_extra_fields = True
473 filter_extra_fields = True
461 username = UnicodeString(
474 username = UnicodeString(
462 strip=True,
475 strip=True,
463 min=1,
476 min=1,
464 not_empty=True,
477 not_empty=True,
465 messages={
478 messages={
466 'empty':_('Please enter a login'),
479 'empty':_('Please enter a login'),
467 'tooShort':_('Enter a value %(min)i characters long or more')}
480 'tooShort':_('Enter a value %(min)i characters long or more')}
468 )
481 )
469
482
470 password = UnicodeString(
483 password = UnicodeString(
471 strip=True,
484 strip=True,
472 min=3,
485 min=3,
473 not_empty=True,
486 not_empty=True,
474 messages={
487 messages={
475 'empty':_('Please enter a password'),
488 'empty':_('Please enter a password'),
476 'tooShort':_('Enter %(min)i characters or more')}
489 'tooShort':_('Enter %(min)i characters or more')}
477 )
490 )
478
491
479
492
480 #chained validators have access to all data
493 #chained validators have access to all data
481 chained_validators = [ValidAuth]
494 chained_validators = [ValidAuth]
482
495
483 def UserForm(edit=False, old_data={}):
496 def UserForm(edit=False, old_data={}):
484 class _UserForm(formencode.Schema):
497 class _UserForm(formencode.Schema):
485 allow_extra_fields = True
498 allow_extra_fields = True
486 filter_extra_fields = True
499 filter_extra_fields = True
487 username = All(UnicodeString(strip=True, min=1, not_empty=True),
500 username = All(UnicodeString(strip=True, min=1, not_empty=True),
488 ValidUsername(edit, old_data))
501 ValidUsername(edit, old_data))
489 if edit:
502 if edit:
490 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
503 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
491 admin = StringBoolean(if_missing=False)
504 admin = StringBoolean(if_missing=False)
492 else:
505 else:
493 password = All(UnicodeString(strip=True, min=6, not_empty=True))
506 password = All(UnicodeString(strip=True, min=6, not_empty=True))
494 active = StringBoolean(if_missing=False)
507 active = StringBoolean(if_missing=False)
495 name = UnicodeString(strip=True, min=1, not_empty=True)
508 name = UnicodeString(strip=True, min=1, not_empty=True)
496 lastname = UnicodeString(strip=True, min=1, not_empty=True)
509 lastname = UnicodeString(strip=True, min=1, not_empty=True)
497 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
510 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
498
511
499 chained_validators = [ValidPassword]
512 chained_validators = [ValidPassword]
500
513
501 return _UserForm
514 return _UserForm
502
515
503
516
504 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
517 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
505 class _UsersGroupForm(formencode.Schema):
518 class _UsersGroupForm(formencode.Schema):
506 allow_extra_fields = True
519 allow_extra_fields = True
507 filter_extra_fields = True
520 filter_extra_fields = True
508
521
509 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
522 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
510 ValidUsersGroup(edit, old_data))
523 ValidUsersGroup(edit, old_data))
511
524
512 users_group_active = StringBoolean(if_missing=False)
525 users_group_active = StringBoolean(if_missing=False)
513
526
514 if edit:
527 if edit:
515 users_group_members = OneOf(available_members, hideList=False,
528 users_group_members = OneOf(available_members, hideList=False,
516 testValueList=True,
529 testValueList=True,
517 if_missing=None, not_empty=False)
530 if_missing=None, not_empty=False)
518
531
519 return _UsersGroupForm
532 return _UsersGroupForm
520
533
521 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
534 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
522 class _ReposGroupForm(formencode.Schema):
535 class _ReposGroupForm(formencode.Schema):
523 allow_extra_fields = True
536 allow_extra_fields = True
524 filter_extra_fields = True
537 filter_extra_fields = True
525
538
526 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
539 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
527 SlugifyName())
540 SlugifyName())
528 group_description = UnicodeString(strip=True, min=1,
541 group_description = UnicodeString(strip=True, min=1,
529 not_empty=True)
542 not_empty=True)
530 group_parent_id = OneOf(available_groups, hideList=False,
543 group_parent_id = OneOf(available_groups, hideList=False,
531 testValueList=True,
544 testValueList=True,
532 if_missing=None, not_empty=False)
545 if_missing=None, not_empty=False)
533
546
534 chained_validators = [ValidReposGroup(edit, old_data)]
547 chained_validators = [ValidReposGroup(edit, old_data)]
535
548
536 return _ReposGroupForm
549 return _ReposGroupForm
537
550
538 def RegisterForm(edit=False, old_data={}):
551 def RegisterForm(edit=False, old_data={}):
539 class _RegisterForm(formencode.Schema):
552 class _RegisterForm(formencode.Schema):
540 allow_extra_fields = True
553 allow_extra_fields = True
541 filter_extra_fields = True
554 filter_extra_fields = True
542 username = All(ValidUsername(edit, old_data),
555 username = All(ValidUsername(edit, old_data),
543 UnicodeString(strip=True, min=1, not_empty=True))
556 UnicodeString(strip=True, min=1, not_empty=True))
544 password = All(UnicodeString(strip=True, min=6, not_empty=True))
557 password = All(UnicodeString(strip=True, min=6, not_empty=True))
545 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
558 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
546 active = StringBoolean(if_missing=False)
559 active = StringBoolean(if_missing=False)
547 name = UnicodeString(strip=True, min=1, not_empty=True)
560 name = UnicodeString(strip=True, min=1, not_empty=True)
548 lastname = UnicodeString(strip=True, min=1, not_empty=True)
561 lastname = UnicodeString(strip=True, min=1, not_empty=True)
549 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
562 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
550
563
551 chained_validators = [ValidPasswordsMatch, ValidPassword]
564 chained_validators = [ValidPasswordsMatch, ValidPassword]
552
565
553 return _RegisterForm
566 return _RegisterForm
554
567
555 def PasswordResetForm():
568 def PasswordResetForm():
556 class _PasswordResetForm(formencode.Schema):
569 class _PasswordResetForm(formencode.Schema):
557 allow_extra_fields = True
570 allow_extra_fields = True
558 filter_extra_fields = True
571 filter_extra_fields = True
559 email = All(ValidSystemEmail(), Email(not_empty=True))
572 email = All(ValidSystemEmail(), Email(not_empty=True))
560 return _PasswordResetForm
573 return _PasswordResetForm
561
574
562 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
575 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
563 repo_groups=[]):
576 repo_groups=[]):
564 class _RepoForm(formencode.Schema):
577 class _RepoForm(formencode.Schema):
565 allow_extra_fields = True
578 allow_extra_fields = True
566 filter_extra_fields = False
579 filter_extra_fields = False
567 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
580 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
568 SlugifyName())
581 SlugifyName())
569 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
582 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
570 ValidCloneUri()())
583 ValidCloneUri()())
571 repo_group = OneOf(repo_groups, hideList=True)
584 repo_group = OneOf(repo_groups, hideList=True)
572 repo_type = OneOf(supported_backends)
585 repo_type = OneOf(supported_backends)
573 description = UnicodeString(strip=True, min=1, not_empty=True)
586 description = UnicodeString(strip=True, min=1, not_empty=True)
574 private = StringBoolean(if_missing=False)
587 private = StringBoolean(if_missing=False)
575 enable_statistics = StringBoolean(if_missing=False)
588 enable_statistics = StringBoolean(if_missing=False)
576 enable_downloads = StringBoolean(if_missing=False)
589 enable_downloads = StringBoolean(if_missing=False)
577
590
578 if edit:
591 if edit:
579 #this is repo owner
592 #this is repo owner
580 user = All(UnicodeString(not_empty=True), ValidRepoUser)
593 user = All(UnicodeString(not_empty=True), ValidRepoUser)
581
594
582 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
595 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
583 return _RepoForm
596 return _RepoForm
584
597
585 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
598 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
586 class _RepoForkForm(formencode.Schema):
599 class _RepoForkForm(formencode.Schema):
587 allow_extra_fields = True
600 allow_extra_fields = True
588 filter_extra_fields = False
601 filter_extra_fields = False
589 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
602 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
590 SlugifyName())
603 SlugifyName())
591 description = UnicodeString(strip=True, min=1, not_empty=True)
604 description = UnicodeString(strip=True, min=1, not_empty=True)
592 private = StringBoolean(if_missing=False)
605 private = StringBoolean(if_missing=False)
593 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
606 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
594
607
595 chained_validators = [ValidForkName()]
608 chained_validators = [ValidForkName()]
596
609
597 return _RepoForkForm
610 return _RepoForkForm
598
611
599 def RepoSettingsForm(edit=False, old_data={}):
612 def RepoSettingsForm(edit=False, old_data={}):
600 class _RepoForm(formencode.Schema):
613 class _RepoForm(formencode.Schema):
601 allow_extra_fields = True
614 allow_extra_fields = True
602 filter_extra_fields = False
615 filter_extra_fields = False
603 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
616 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
604 SlugifyName())
617 SlugifyName())
605 description = UnicodeString(strip=True, min=1, not_empty=True)
618 description = UnicodeString(strip=True, min=1, not_empty=True)
606 private = StringBoolean(if_missing=False)
619 private = StringBoolean(if_missing=False)
607
620
608 chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings]
621 chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings]
609 return _RepoForm
622 return _RepoForm
610
623
611
624
612 def ApplicationSettingsForm():
625 def ApplicationSettingsForm():
613 class _ApplicationSettingsForm(formencode.Schema):
626 class _ApplicationSettingsForm(formencode.Schema):
614 allow_extra_fields = True
627 allow_extra_fields = True
615 filter_extra_fields = False
628 filter_extra_fields = False
616 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
629 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
617 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
630 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
618 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
631 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
619
632
620 return _ApplicationSettingsForm
633 return _ApplicationSettingsForm
621
634
622 def ApplicationUiSettingsForm():
635 def ApplicationUiSettingsForm():
623 class _ApplicationUiSettingsForm(formencode.Schema):
636 class _ApplicationUiSettingsForm(formencode.Schema):
624 allow_extra_fields = True
637 allow_extra_fields = True
625 filter_extra_fields = False
638 filter_extra_fields = False
626 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
639 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
627 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
640 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
628 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
641 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
629 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
642 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
630 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
643 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
631 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
644 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
632
645
633 return _ApplicationUiSettingsForm
646 return _ApplicationUiSettingsForm
634
647
635 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
648 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
636 class _DefaultPermissionsForm(formencode.Schema):
649 class _DefaultPermissionsForm(formencode.Schema):
637 allow_extra_fields = True
650 allow_extra_fields = True
638 filter_extra_fields = True
651 filter_extra_fields = True
639 overwrite_default = StringBoolean(if_missing=False)
652 overwrite_default = StringBoolean(if_missing=False)
640 anonymous = OneOf(['True', 'False'], if_missing=False)
653 anonymous = OneOf(['True', 'False'], if_missing=False)
641 default_perm = OneOf(perms_choices)
654 default_perm = OneOf(perms_choices)
642 default_register = OneOf(register_choices)
655 default_register = OneOf(register_choices)
643 default_create = OneOf(create_choices)
656 default_create = OneOf(create_choices)
644
657
645 return _DefaultPermissionsForm
658 return _DefaultPermissionsForm
646
659
647
660
648 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
661 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
649 class _LdapSettingsForm(formencode.Schema):
662 class _LdapSettingsForm(formencode.Schema):
650 allow_extra_fields = True
663 allow_extra_fields = True
651 filter_extra_fields = True
664 filter_extra_fields = True
652 pre_validators = [LdapLibValidator]
665 pre_validators = [LdapLibValidator]
653 ldap_active = StringBoolean(if_missing=False)
666 ldap_active = StringBoolean(if_missing=False)
654 ldap_host = UnicodeString(strip=True,)
667 ldap_host = UnicodeString(strip=True,)
655 ldap_port = Number(strip=True,)
668 ldap_port = Number(strip=True,)
656 ldap_tls_kind = OneOf(tls_kind_choices)
669 ldap_tls_kind = OneOf(tls_kind_choices)
657 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
670 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
658 ldap_dn_user = UnicodeString(strip=True,)
671 ldap_dn_user = UnicodeString(strip=True,)
659 ldap_dn_pass = UnicodeString(strip=True,)
672 ldap_dn_pass = UnicodeString(strip=True,)
660 ldap_base_dn = UnicodeString(strip=True,)
673 ldap_base_dn = UnicodeString(strip=True,)
661 ldap_filter = UnicodeString(strip=True,)
674 ldap_filter = UnicodeString(strip=True,)
662 ldap_search_scope = OneOf(search_scope_choices)
675 ldap_search_scope = OneOf(search_scope_choices)
663 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
676 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
664 ldap_attr_firstname = UnicodeString(strip=True,)
677 ldap_attr_firstname = UnicodeString(strip=True,)
665 ldap_attr_lastname = UnicodeString(strip=True,)
678 ldap_attr_lastname = UnicodeString(strip=True,)
666 ldap_attr_email = UnicodeString(strip=True,)
679 ldap_attr_email = UnicodeString(strip=True,)
667
680
668 return _LdapSettingsForm
681 return _LdapSettingsForm
@@ -1,148 +1,148 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.tests.test_crawer
3 rhodecode.tests.test_crawer
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Test for crawling a project for memory usage
6 Test for crawling a project for memory usage
7 This should be runned just as regular script together
7 This should be runned just as regular script together
8 with a watch script that will show memory usage.
8 with a watch script that will show memory usage.
9
9
10 watch -n1 ./rhodecode/tests/mem_watch
10 watch -n1 ./rhodecode/tests/mem_watch
11
11
12 :created_on: Apr 21, 2010
12 :created_on: Apr 21, 2010
13 :author: marcink
13 :author: marcink
14 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
14 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
15 :license: GPLv3, see COPYING for more details.
15 :license: GPLv3, see COPYING for more details.
16 """
16 """
17 # This program is free software: you can redistribute it and/or modify
17 # This program is free software: you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation, either version 3 of the License, or
19 # the Free Software Foundation, either version 3 of the License, or
20 # (at your option) any later version.
20 # (at your option) any later version.
21 #
21 #
22 # This program is distributed in the hope that it will be useful,
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 # GNU General Public License for more details.
25 # GNU General Public License for more details.
26 #
26 #
27 # You should have received a copy of the GNU General Public License
27 # You should have received a copy of the GNU General Public License
28 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 # along with this program. If not, see <http://www.gnu.org/licenses/>.
29
29
30
30
31 import cookielib
31 import cookielib
32 import urllib
32 import urllib
33 import urllib2
33 import urllib2
34 import vcs
34 import vcs
35 import time
35 import time
36
36
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39
39
40 BASE_URI = 'http://127.0.0.1:5000/%s'
40 BASE_URI = 'http://127.0.0.1:5000/%s'
41 PROJECT = 'CPython'
41 PROJECT = 'CPython'
42 PROJECT_PATH = jn('/', 'home', 'marcink', 'hg_repos')
42 PROJECT_PATH = jn('/', 'home', 'marcink', 'hg_repos')
43
43
44
44
45 cj = cookielib.FileCookieJar('/tmp/rc_test_cookie.txt')
45 cj = cookielib.FileCookieJar('/tmp/rc_test_cookie.txt')
46 o = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
46 o = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
47 o.addheaders = [
47 o.addheaders = [
48 ('User-agent', 'rhodecode-crawler'),
48 ('User-agent', 'rhodecode-crawler'),
49 ('Accept-Language', 'en - us, en;q = 0.5')
49 ('Accept-Language', 'en - us, en;q = 0.5')
50 ]
50 ]
51
51
52 urllib2.install_opener(o)
52 urllib2.install_opener(o)
53
53
54
54
55
55
56 def test_changelog_walk(pages=100):
56 def test_changelog_walk(pages=100):
57 total_time = 0
57 total_time = 0
58 for i in range(1, pages):
58 for i in range(1, pages):
59
59
60 page = '/'.join((PROJECT, 'changelog',))
60 page = '/'.join((PROJECT, 'changelog',))
61
61
62 full_uri = (BASE_URI % page) + '?' + urllib.urlencode({'page':i})
62 full_uri = (BASE_URI % page) + '?' + urllib.urlencode({'page':i})
63 s = time.time()
63 s = time.time()
64 f = o.open(full_uri)
64 f = o.open(full_uri)
65 size = len(f.read())
65 size = len(f.read())
66 e = time.time() - s
66 e = time.time() - s
67 total_time += e
67 total_time += e
68 print 'visited %s size:%s req:%s ms' % (full_uri, size, e)
68 print 'visited %s size:%s req:%s ms' % (full_uri, size, e)
69
69
70
70
71 print 'total_time', total_time
71 print 'total_time', total_time
72 print 'average on req', total_time / float(pages)
72 print 'average on req', total_time / float(pages)
73
73
74
74
75 def test_changeset_walk(limit=None):
75 def test_changeset_walk(limit=None):
76 print 'processing', jn(PROJECT_PATH, PROJECT)
76 print 'processing', jn(PROJECT_PATH, PROJECT)
77 total_time = 0
77 total_time = 0
78
78
79 repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT))
79 repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT))
80 cnt = 0
80 cnt = 0
81 for i in repo:
81 for i in repo:
82 cnt += 1
82 cnt += 1
83 raw_cs = '/'.join((PROJECT, 'changeset', i.raw_id))
83 raw_cs = '/'.join((PROJECT, 'changeset', i.raw_id))
84 if limit and limit == cnt:
84 if limit and limit == cnt:
85 break
85 break
86
86
87 full_uri = (BASE_URI % raw_cs)
87 full_uri = (BASE_URI % raw_cs)
88 s = time.time()
88 s = time.time()
89 f = o.open(full_uri)
89 f = o.open(full_uri)
90 size = len(f.read())
90 size = len(f.read())
91 e = time.time() - s
91 e = time.time() - s
92 total_time += e
92 total_time += e
93 print '%s visited %s\%s size:%s req:%s ms' % (cnt, full_uri, i, size, e)
93 print '%s visited %s\%s size:%s req:%s ms' % (cnt, full_uri, i, size, e)
94
94
95 print 'total_time', total_time
95 print 'total_time', total_time
96 print 'average on req', total_time / float(cnt)
96 print 'average on req', total_time / float(cnt)
97
97
98
98
99 def test_files_walk(limit=100):
99 def test_files_walk(limit=100):
100 print 'processing', jn(PROJECT_PATH, PROJECT)
100 print 'processing', jn(PROJECT_PATH, PROJECT)
101 total_time = 0
101 total_time = 0
102
102
103 repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT))
103 repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT))
104
104
105 from rhodecode.lib.oset import OrderedSet
105 from rhodecode.lib.compat import OrderedSet
106
106
107 paths_ = OrderedSet([''])
107 paths_ = OrderedSet([''])
108 try:
108 try:
109 tip = repo.get_changeset('tip')
109 tip = repo.get_changeset('tip')
110 for topnode, dirs, files in tip.walk('/'):
110 for topnode, dirs, files in tip.walk('/'):
111
111
112 for dir in dirs:
112 for dir in dirs:
113 paths_.add(dir.path)
113 paths_.add(dir.path)
114 for f in dir:
114 for f in dir:
115 paths_.add(f.path)
115 paths_.add(f.path)
116
116
117 for f in files:
117 for f in files:
118 paths_.add(f.path)
118 paths_.add(f.path)
119
119
120 except vcs.exception.RepositoryError, e:
120 except vcs.exception.RepositoryError, e:
121 pass
121 pass
122
122
123 cnt = 0
123 cnt = 0
124 for f in paths_:
124 for f in paths_:
125 cnt += 1
125 cnt += 1
126 if limit and limit == cnt:
126 if limit and limit == cnt:
127 break
127 break
128
128
129 file_path = '/'.join((PROJECT, 'files', 'tip', f))
129 file_path = '/'.join((PROJECT, 'files', 'tip', f))
130
130
131 full_uri = (BASE_URI % file_path)
131 full_uri = (BASE_URI % file_path)
132 s = time.time()
132 s = time.time()
133 f = o.open(full_uri)
133 f = o.open(full_uri)
134 size = len(f.read())
134 size = len(f.read())
135 e = time.time() - s
135 e = time.time() - s
136 total_time += e
136 total_time += e
137 print '%s visited %s size:%s req:%s ms' % (cnt, full_uri, size, e)
137 print '%s visited %s size:%s req:%s ms' % (cnt, full_uri, size, e)
138
138
139 print 'total_time', total_time
139 print 'total_time', total_time
140 print 'average on req', total_time / float(cnt)
140 print 'average on req', total_time / float(cnt)
141
141
142
142
143
143
144 test_changelog_walk(40)
144 test_changelog_walk(40)
145 time.sleep(2)
145 time.sleep(2)
146 test_changeset_walk(limit=100)
146 test_changeset_walk(limit=100)
147 time.sleep(2)
147 time.sleep(2)
148 test_files_walk(100)
148 test_files_walk(100)
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now