##// END OF EJS Templates
Merge with upstream
Nicolas VINOT -
r1590:10d11754 merge 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,148 +1,149 b''
1 =================================================
1 =================================================
2 Welcome to RhodeCode (RhodiumCode) documentation!
2 Welcome to RhodeCode (RhodiumCode) documentation!
3 =================================================
3 =================================================
4
4
5 ``RhodeCode`` (formerly hg-app) is a Pylons framework based Mercurial repository
5 ``RhodeCode`` is a Pylons framework based Mercurial repository
6 browser/management tool with a built in push/pull server and full text search.
6 browser/management tool with a built in push/pull server and full text search.
7 It works on http/https and has a built in permission/authentication system with
7 It works on http/https and has a built in permission/authentication system with
8 the ability to authenticate via LDAP.
8 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also supports
9 simple API so it's easy integrable with existing systems.
9
10
10 RhodeCode is similar in some respects to github or bitbucket_,
11 RhodeCode is similar in some respects to github or bitbucket_,
11 however RhodeCode can be run as standalone hosted application on your own server.
12 however RhodeCode can be run as standalone hosted application on your own server.
12 It is open source and donation ware and focuses more on providing a customized,
13 It is open source and donation ware and focuses more on providing a customized,
13 self administered interface for Mercurial(and soon GIT) repositories.
14 self administered interface for Mercurial(and soon GIT) repositories.
14 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
15 handle multiple different version control systems.
16 handle multiple different version control systems.
16
17
17 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
18
19
19 RhodeCode demo
20 RhodeCode demo
20 --------------
21 --------------
21
22
22 http://demo.rhodecode.org
23 http://demo.rhodecode.org
23
24
24 The default access is anonymous but you can login to an administrative account
25 The default access is anonymous but you can login to an administrative account
25 using the following credentials:
26 using the following credentials:
26
27
27 - username: demo
28 - username: demo
28 - password: demo12
29 - password: demo12
29
30
30 Source code
31 Source code
31 -----------
32 -----------
32
33
33 The latest sources can be obtained from official RhodeCode instance
34 The latest sources can be obtained from official RhodeCode instance
34 https://secure.rhodecode.org
35 https://secure.rhodecode.org
35
36
36
37
37 MIRRORS:
38 MIRRORS:
38
39
39 Issue tracker and sources at bitbucket_
40 Issue tracker and sources at bitbucket_
40
41
41 http://bitbucket.org/marcinkuzminski/rhodecode
42 http://bitbucket.org/marcinkuzminski/rhodecode
42
43
43 Sources at github_
44 Sources at github_
44
45
45 https://github.com/marcinkuzminski/rhodecode
46 https://github.com/marcinkuzminski/rhodecode
46
47
47 Installation
48 Installation
48 ------------
49 ------------
49
50
50 Please visit http://packages.python.org/RhodeCode/installation.html
51 Please visit http://packages.python.org/RhodeCode/installation.html
51
52
52
53
53 RhodeCode Features
54 RhodeCode Features
54 ------------------
55 ------------------
55
56
56 - Has it's own middleware to handle mercurial_ protocol requests.
57 - Has it's own middleware to handle mercurial_ protocol requests.
57 Each request can be logged and authenticated.
58 Each request can be logged and authenticated.
58 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
59 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
59 Supports http/https and LDAP
60 Supports http/https and LDAP
60 - Full permissions (private/read/write/admin) and authentication per project.
61 - Full permissions (private/read/write/admin) and authentication per project.
61 One account for web interface and mercurial_ push/pull/clone operations.
62 One account for web interface and mercurial_ push/pull/clone operations.
62 - Have built in users groups for easier permission management
63 - Have built in users groups for easier permission management
63 - Repository groups let you group repos and manage them easier.
64 - Repository groups let you group repos and manage them easier.
64 - Users can fork other users repo. RhodeCode have also compare view to see
65 - Users can fork other users repo. RhodeCode have also compare view to see
65 combined changeset for all changeset made within single push.
66 combined changeset for all changeset made within single push.
66 - Build in commit-api let's you add, edit and commit files right from RhodeCode
67 - Build in commit-api let's you add, edit and commit files right from RhodeCode
67 interface using simple editor or upload form for binaries.
68 interface using simple editor or upload form for binaries.
68 - Mako templates let's you customize the look and feel of the application.
69 - Mako templates let's you customize the look and feel of the application.
69 - Beautiful diffs, annotations and source code browsing all colored by pygments.
70 - Beautiful diffs, annotations and source code browsing all colored by pygments.
70 Raw diffs are made in git-diff format, including git_ binary-patches
71 Raw diffs are made in git-diff format, including git_ binary-patches
71 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
72 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
72 - Admin interface with user/permission management. Admin activity journal, logs
73 - Admin interface with user/permission management. Admin activity journal, logs
73 pulls, pushes, forks, registrations and other actions made by all users.
74 pulls, pushes, forks, registrations and other actions made by all users.
74 - Server side forks. It is possible to fork a project and modify it freely
75 - Server side forks. It is possible to fork a project and modify it freely
75 without breaking the main repository. You can even write Your own hooks
76 without breaking the main repository. You can even write Your own hooks
76 and install them
77 and install them
77 - Full text search powered by Whoosh on the source files, and file names.
78 - Full text search powered by Whoosh on the source files, and file names.
78 Build in indexing daemons, with optional incremental index build
79 Build in indexing daemons, with optional incremental index build
79 (no external search servers required all in one application)
80 (no external search servers required all in one application)
80 - Setup project descriptions and info inside built in db for easy, non
81 - Setup project descriptions and info inside built in db for easy, non
81 file-system operations
82 file-system operations
82 - Intelligent cache with invalidation after push or project change, provides
83 - Intelligent cache with invalidation after push or project change, provides
83 high performance and always up to date data.
84 high performance and always up to date data.
84 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
85 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
85 - Async tasks for speed and performance using celery_ (works without them too)
86 - Async tasks for speed and performance using celery_ (works without them too)
86 - Backup scripts can do backup of whole app and send it over scp to desired
87 - Backup scripts can do backup of whole app and send it over scp to desired
87 location
88 location
88 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
89 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
89
90
90
91
91 .. include:: ./docs/screenshots.rst
92 .. include:: ./docs/screenshots.rst
92
93
93
94
94 Incoming / Plans
95 Incoming / Plans
95 ----------------
96 ----------------
96
97
97 - Finer granular permissions per branch, repo group or subrepo
98 - Finer granular permissions per branch, repo group or subrepo
98 - pull requests and web based merges
99 - pull requests and web based merges
99 - notification and message system
100 - notification and message system
100 - SSH based authentication with server side key management
101 - SSH based authentication with server side key management
101 - Code review (probably based on hg-review)
102 - Code review (probably based on hg-review)
102 - Full git_ support, with push/pull server (currently in beta tests)
103 - Full git_ support, with push/pull server (currently in beta tests)
103 - Redmine and other bugtrackers integration
104 - Redmine and other bugtrackers integration
104 - Commit based built in wiki system
105 - Commit based built in wiki system
105 - More statistics and graph (global annotation + some more statistics)
106 - More statistics and graph (global annotation + some more statistics)
106 - Other advancements as development continues (or you can of course make
107 - Other advancements as development continues (or you can of course make
107 additions and or requests)
108 additions and or requests)
108
109
109 License
110 License
110 -------
111 -------
111
112
112 ``RhodeCode`` is released under the GPLv3 license.
113 ``RhodeCode`` is released under the GPLv3 license.
113
114
114
115
115 Mailing group Q&A
116 Mailing group Q&A
116 -----------------
117 -----------------
117
118
118 Join the `Google group <http://groups.google.com/group/rhodecode>`_
119 Join the `Google group <http://groups.google.com/group/rhodecode>`_
119
120
120 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
121 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
121
122
122 Join #rhodecode on FreeNode (irc.freenode.net)
123 Join #rhodecode on FreeNode (irc.freenode.net)
123 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
124 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
124
125
125 Online documentation
126 Online documentation
126 --------------------
127 --------------------
127
128
128 Online documentation for the current version of RhodeCode is available at
129 Online documentation for the current version of RhodeCode is available at
129 http://packages.python.org/RhodeCode/.
130 http://packages.python.org/RhodeCode/.
130 You may also build the documentation for yourself - go into ``docs/`` and run::
131 You may also build the documentation for yourself - go into ``docs/`` and run::
131
132
132 make html
133 make html
133
134
134 (You need to have sphinx_ installed to build the documentation. If you don't
135 (You need to have sphinx_ installed to build the documentation. If you don't
135 have sphinx_ installed you can install it via the command:
136 have sphinx_ installed you can install it via the command:
136 ``easy_install sphinx``)
137 ``easy_install sphinx``)
137
138
138 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
139 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
139 .. _python: http://www.python.org/
140 .. _python: http://www.python.org/
140 .. _sphinx: http://sphinx.pocoo.org/
141 .. _sphinx: http://sphinx.pocoo.org/
141 .. _mercurial: http://mercurial.selenic.com/
142 .. _mercurial: http://mercurial.selenic.com/
142 .. _bitbucket: http://bitbucket.org/
143 .. _bitbucket: http://bitbucket.org/
143 .. _github: http://github.com/
144 .. _github: http://github.com/
144 .. _subversion: http://subversion.tigris.org/
145 .. _subversion: http://subversion.tigris.org/
145 .. _git: http://git-scm.com/
146 .. _git: http://git-scm.com/
146 .. _celery: http://celeryproject.org/
147 .. _celery: http://celeryproject.org/
147 .. _Sphinx: http://sphinx.pocoo.org/
148 .. _Sphinx: http://sphinx.pocoo.org/
148 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
149 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,341 +1,354 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 Changelog
3 Changelog
4 =========
4 =========
5
5
6 1.2.0 (**2011-XX-XX**)
6
7 1.3.0 (**XXXX-XX-XX**)
7 ======================
8 ======================
8
9
9 :status: in-progress
10 :status: in-progress
10 :branch: beta
11 :branch: beta
11
12
12 news
13 news
13 ----
14 ----
14
15
16 fixes
17 -----
18
19
20
21
22 1.2.0 (**2011-10-07**)
23 ======================
24
25 news
26 ----
27
15 - implemented #47 repository groups
28 - implemented #47 repository groups
16 - implemented #89 Can setup google analytics code from settings menu
29 - implemented #89 Can setup google analytics code from settings menu
17 - implemented #91 added nicer looking archive urls with more download options
30 - implemented #91 added nicer looking archive urls with more download options
18 like tags, branches
31 like tags, branches
19 - implemented #44 into file browsing, and added follow branch option
32 - implemented #44 into file browsing, and added follow branch option
20 - implemented #84 downloads can be enabled/disabled for each repository
33 - implemented #84 downloads can be enabled/disabled for each repository
21 - anonymous repository can be cloned without having to pass default:default
34 - anonymous repository can be cloned without having to pass default:default
22 into clone url
35 into clone url
23 - fixed #90 whoosh indexer can index chooses repositories passed in command
36 - fixed #90 whoosh indexer can index chooses repositories passed in command
24 line
37 line
25 - extended journal with day aggregates and paging
38 - extended journal with day aggregates and paging
26 - implemented #107 source code lines highlight ranges
39 - implemented #107 source code lines highlight ranges
27 - implemented #93 customizable changelog on combined revision ranges -
40 - implemented #93 customizable changelog on combined revision ranges -
28 equivalent of githubs compare view
41 equivalent of githubs compare view
29 - implemented #108 extended and more powerful LDAP configuration
42 - implemented #108 extended and more powerful LDAP configuration
30 - implemented #56 users groups
43 - implemented #56 users groups
31 - major code rewrites optimized codes for speed and memory usage
44 - major code rewrites optimized codes for speed and memory usage
32 - raw and diff downloads are now in git format
45 - raw and diff downloads are now in git format
33 - setup command checks for write access to given path
46 - setup command checks for write access to given path
34 - fixed many issues with international characters and unicode. It uses utf8
47 - fixed many issues with international characters and unicode. It uses utf8
35 decode with replace to provide less errors even with non utf8 encoded strings
48 decode with replace to provide less errors even with non utf8 encoded strings
36 - #125 added API KEY access to feeds
49 - #125 added API KEY access to feeds
37 - #109 Repository can be created from external Mercurial link (aka. remote
50 - #109 Repository can be created from external Mercurial link (aka. remote
38 repository, and manually updated (via pull) from admin panel
51 repository, and manually updated (via pull) from admin panel
39 - beta git support - push/pull server + basic view for git repos
52 - beta git support - push/pull server + basic view for git repos
40 - added followers page and forks page
53 - added followers page and forks page
41 - server side file creation (with binary file upload interface)
54 - server side file creation (with binary file upload interface)
42 and edition with commits powered by codemirror
55 and edition with commits powered by codemirror
43 - #111 file browser file finder, quick lookup files on whole file tree
56 - #111 file browser file finder, quick lookup files on whole file tree
44 - added quick login sliding menu into main page
57 - added quick login sliding menu into main page
45 - changelog uses lazy loading of affected files details, in some scenarios
58 - changelog uses lazy loading of affected files details, in some scenarios
46 this can improve speed of changelog page dramatically especially for
59 this can improve speed of changelog page dramatically especially for
47 larger repositories.
60 larger repositories.
48 - implements #214 added support for downloading subrepos in download menu.
61 - implements #214 added support for downloading subrepos in download menu.
49 - Added basic API for direct operations on rhodecode via JSON
62 - Added basic API for direct operations on rhodecode via JSON
50 - Implemented advanced hook management
63 - Implemented advanced hook management
51
64
52 fixes
65 fixes
53 -----
66 -----
54
67
55 - fixed file browser bug, when switching into given form revision the url was
68 - fixed file browser bug, when switching into given form revision the url was
56 not changing
69 not changing
57 - fixed propagation to error controller on simplehg and simplegit middlewares
70 - fixed propagation to error controller on simplehg and simplegit middlewares
58 - fixed error when trying to make a download on empty repository
71 - fixed error when trying to make a download on empty repository
59 - fixed problem with '[' chars in commit messages in journal
72 - fixed problem with '[' chars in commit messages in journal
60 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
73 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
61 - journal fork fixes
74 - journal fork fixes
62 - removed issue with space inside renamed repository after deletion
75 - removed issue with space inside renamed repository after deletion
63 - fixed strange issue on formencode imports
76 - fixed strange issue on formencode imports
64 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
77 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
65 - #150 fixes for errors on repositories mapped in db but corrupted in
78 - #150 fixes for errors on repositories mapped in db but corrupted in
66 filesystem
79 filesystem
67 - fixed problem with ascendant characters in realm #181
80 - fixed problem with ascendant characters in realm #181
68 - fixed problem with sqlite file based database connection pool
81 - fixed problem with sqlite file based database connection pool
69 - whoosh indexer and code stats share the same dynamic extensions map
82 - whoosh indexer and code stats share the same dynamic extensions map
70 - fixes #188 - relationship delete of repo_to_perm entry on user removal
83 - fixes #188 - relationship delete of repo_to_perm entry on user removal
71 - fixes issue #189 Trending source files shows "show more" when no more exist
84 - fixes issue #189 Trending source files shows "show more" when no more exist
72 - fixes issue #197 Relative paths for pidlocks
85 - fixes issue #197 Relative paths for pidlocks
73 - fixes issue #198 password will require only 3 chars now for login form
86 - fixes issue #198 password will require only 3 chars now for login form
74 - fixes issue #199 wrong redirection for non admin users after creating a repository
87 - fixes issue #199 wrong redirection for non admin users after creating a repository
75 - fixes issues #202, bad db constraint made impossible to attach same group
88 - fixes issues #202, bad db constraint made impossible to attach same group
76 more than one time. Affects only mysql/postgres
89 more than one time. Affects only mysql/postgres
77 - fixes #218 os.kill patch for windows was missing sig param
90 - fixes #218 os.kill patch for windows was missing sig param
78 - improved rendering of dag (they are not trimmed anymore when number of
91 - improved rendering of dag (they are not trimmed anymore when number of
79 heads exceeds 5)
92 heads exceeds 5)
80
93
81 1.1.8 (**2011-04-12**)
94 1.1.8 (**2011-04-12**)
82 ======================
95 ======================
83
96
84 news
97 news
85 ----
98 ----
86
99
87 - improved windows support
100 - improved windows support
88
101
89 fixes
102 fixes
90 -----
103 -----
91
104
92 - fixed #140 freeze of python dateutil library, since new version is python2.x
105 - fixed #140 freeze of python dateutil library, since new version is python2.x
93 incompatible
106 incompatible
94 - setup-app will check for write permission in given path
107 - setup-app will check for write permission in given path
95 - cleaned up license info issue #149
108 - cleaned up license info issue #149
96 - fixes for issues #137,#116 and problems with unicode and accented characters.
109 - fixes for issues #137,#116 and problems with unicode and accented characters.
97 - fixes crashes on gravatar, when passed in email as unicode
110 - fixes crashes on gravatar, when passed in email as unicode
98 - fixed tooltip flickering problems
111 - fixed tooltip flickering problems
99 - fixed came_from redirection on windows
112 - fixed came_from redirection on windows
100 - fixed logging modules, and sql formatters
113 - fixed logging modules, and sql formatters
101 - windows fixes for os.kill issue #133
114 - windows fixes for os.kill issue #133
102 - fixes path splitting for windows issues #148
115 - fixes path splitting for windows issues #148
103 - fixed issue #143 wrong import on migration to 1.1.X
116 - fixed issue #143 wrong import on migration to 1.1.X
104 - fixed problems with displaying binary files, thanks to Thomas Waldmann
117 - fixed problems with displaying binary files, thanks to Thomas Waldmann
105 - removed name from archive files since it's breaking ui for long repo names
118 - removed name from archive files since it's breaking ui for long repo names
106 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
119 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
107 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
120 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
108 Thomas Waldmann
121 Thomas Waldmann
109 - fixed issue #166 summary pager was skipping 10 revisions on second page
122 - fixed issue #166 summary pager was skipping 10 revisions on second page
110
123
111
124
112 1.1.7 (**2011-03-23**)
125 1.1.7 (**2011-03-23**)
113 ======================
126 ======================
114
127
115 news
128 news
116 ----
129 ----
117
130
118 fixes
131 fixes
119 -----
132 -----
120
133
121 - fixed (again) #136 installation support for FreeBSD
134 - fixed (again) #136 installation support for FreeBSD
122
135
123
136
124 1.1.6 (**2011-03-21**)
137 1.1.6 (**2011-03-21**)
125 ======================
138 ======================
126
139
127 news
140 news
128 ----
141 ----
129
142
130 fixes
143 fixes
131 -----
144 -----
132
145
133 - fixed #136 installation support for FreeBSD
146 - fixed #136 installation support for FreeBSD
134 - RhodeCode will check for python version during installation
147 - RhodeCode will check for python version during installation
135
148
136 1.1.5 (**2011-03-17**)
149 1.1.5 (**2011-03-17**)
137 ======================
150 ======================
138
151
139 news
152 news
140 ----
153 ----
141
154
142 - basic windows support, by exchanging pybcrypt into sha256 for windows only
155 - basic windows support, by exchanging pybcrypt into sha256 for windows only
143 highly inspired by idea of mantis406
156 highly inspired by idea of mantis406
144
157
145 fixes
158 fixes
146 -----
159 -----
147
160
148 - fixed sorting by author in main page
161 - fixed sorting by author in main page
149 - fixed crashes with diffs on binary files
162 - fixed crashes with diffs on binary files
150 - fixed #131 problem with boolean values for LDAP
163 - fixed #131 problem with boolean values for LDAP
151 - fixed #122 mysql problems thanks to striker69
164 - fixed #122 mysql problems thanks to striker69
152 - fixed problem with errors on calling raw/raw_files/annotate functions
165 - fixed problem with errors on calling raw/raw_files/annotate functions
153 with unknown revisions
166 with unknown revisions
154 - fixed returned rawfiles attachment names with international character
167 - fixed returned rawfiles attachment names with international character
155 - cleaned out docs, big thanks to Jason Harris
168 - cleaned out docs, big thanks to Jason Harris
156
169
157 1.1.4 (**2011-02-19**)
170 1.1.4 (**2011-02-19**)
158 ======================
171 ======================
159
172
160 news
173 news
161 ----
174 ----
162
175
163 fixes
176 fixes
164 -----
177 -----
165
178
166 - fixed formencode import problem on settings page, that caused server crash
179 - fixed formencode import problem on settings page, that caused server crash
167 when that page was accessed as first after server start
180 when that page was accessed as first after server start
168 - journal fixes
181 - journal fixes
169 - fixed option to access repository just by entering http://server/<repo_name>
182 - fixed option to access repository just by entering http://server/<repo_name>
170
183
171 1.1.3 (**2011-02-16**)
184 1.1.3 (**2011-02-16**)
172 ======================
185 ======================
173
186
174 news
187 news
175 ----
188 ----
176
189
177 - implemented #102 allowing the '.' character in username
190 - implemented #102 allowing the '.' character in username
178 - added option to access repository just by entering http://server/<repo_name>
191 - added option to access repository just by entering http://server/<repo_name>
179 - celery task ignores result for better performance
192 - celery task ignores result for better performance
180
193
181 fixes
194 fixes
182 -----
195 -----
183
196
184 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
197 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
185 apollo13 and Johan Walles
198 apollo13 and Johan Walles
186 - small fixes in journal
199 - small fixes in journal
187 - fixed problems with getting setting for celery from .ini files
200 - fixed problems with getting setting for celery from .ini files
188 - registration, password reset and login boxes share the same title as main
201 - registration, password reset and login boxes share the same title as main
189 application now
202 application now
190 - fixed #113: to high permissions to fork repository
203 - fixed #113: to high permissions to fork repository
191 - fixed problem with '[' chars in commit messages in journal
204 - fixed problem with '[' chars in commit messages in journal
192 - removed issue with space inside renamed repository after deletion
205 - removed issue with space inside renamed repository after deletion
193 - db transaction fixes when filesystem repository creation failed
206 - db transaction fixes when filesystem repository creation failed
194 - fixed #106 relation issues on databases different than sqlite
207 - fixed #106 relation issues on databases different than sqlite
195 - fixed static files paths links to use of url() method
208 - fixed static files paths links to use of url() method
196
209
197 1.1.2 (**2011-01-12**)
210 1.1.2 (**2011-01-12**)
198 ======================
211 ======================
199
212
200 news
213 news
201 ----
214 ----
202
215
203
216
204 fixes
217 fixes
205 -----
218 -----
206
219
207 - fixes #98 protection against float division of percentage stats
220 - fixes #98 protection against float division of percentage stats
208 - fixed graph bug
221 - fixed graph bug
209 - forced webhelpers version since it was making troubles during installation
222 - forced webhelpers version since it was making troubles during installation
210
223
211 1.1.1 (**2011-01-06**)
224 1.1.1 (**2011-01-06**)
212 ======================
225 ======================
213
226
214 news
227 news
215 ----
228 ----
216
229
217 - added force https option into ini files for easier https usage (no need to
230 - added force https option into ini files for easier https usage (no need to
218 set server headers with this options)
231 set server headers with this options)
219 - small css updates
232 - small css updates
220
233
221 fixes
234 fixes
222 -----
235 -----
223
236
224 - fixed #96 redirect loop on files view on repositories without changesets
237 - fixed #96 redirect loop on files view on repositories without changesets
225 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
238 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
226 and server crashed with errors
239 and server crashed with errors
227 - fixed large tooltips problems on main page
240 - fixed large tooltips problems on main page
228 - fixed #92 whoosh indexer is more error proof
241 - fixed #92 whoosh indexer is more error proof
229
242
230 1.1.0 (**2010-12-18**)
243 1.1.0 (**2010-12-18**)
231 ======================
244 ======================
232
245
233 news
246 news
234 ----
247 ----
235
248
236 - rewrite of internals for vcs >=0.1.10
249 - rewrite of internals for vcs >=0.1.10
237 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
250 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
238 with older clients
251 with older clients
239 - anonymous access, authentication via ldap
252 - anonymous access, authentication via ldap
240 - performance upgrade for cached repos list - each repository has it's own
253 - performance upgrade for cached repos list - each repository has it's own
241 cache that's invalidated when needed.
254 cache that's invalidated when needed.
242 - performance upgrades on repositories with large amount of commits (20K+)
255 - performance upgrades on repositories with large amount of commits (20K+)
243 - main page quick filter for filtering repositories
256 - main page quick filter for filtering repositories
244 - user dashboards with ability to follow chosen repositories actions
257 - user dashboards with ability to follow chosen repositories actions
245 - sends email to admin on new user registration
258 - sends email to admin on new user registration
246 - added cache/statistics reset options into repository settings
259 - added cache/statistics reset options into repository settings
247 - more detailed action logger (based on hooks) with pushed changesets lists
260 - more detailed action logger (based on hooks) with pushed changesets lists
248 and options to disable those hooks from admin panel
261 and options to disable those hooks from admin panel
249 - introduced new enhanced changelog for merges that shows more accurate results
262 - introduced new enhanced changelog for merges that shows more accurate results
250 - new improved and faster code stats (based on pygments lexers mapping tables,
263 - new improved and faster code stats (based on pygments lexers mapping tables,
251 showing up to 10 trending sources for each repository. Additionally stats
264 showing up to 10 trending sources for each repository. Additionally stats
252 can be disabled in repository settings.
265 can be disabled in repository settings.
253 - gui optimizations, fixed application width to 1024px
266 - gui optimizations, fixed application width to 1024px
254 - added cut off (for large files/changesets) limit into config files
267 - added cut off (for large files/changesets) limit into config files
255 - whoosh, celeryd, upgrade moved to paster command
268 - whoosh, celeryd, upgrade moved to paster command
256 - other than sqlite database backends can be used
269 - other than sqlite database backends can be used
257
270
258 fixes
271 fixes
259 -----
272 -----
260
273
261 - fixes #61 forked repo was showing only after cache expired
274 - fixes #61 forked repo was showing only after cache expired
262 - fixes #76 no confirmation on user deletes
275 - fixes #76 no confirmation on user deletes
263 - fixes #66 Name field misspelled
276 - fixes #66 Name field misspelled
264 - fixes #72 block user removal when he owns repositories
277 - fixes #72 block user removal when he owns repositories
265 - fixes #69 added password confirmation fields
278 - fixes #69 added password confirmation fields
266 - fixes #87 RhodeCode crashes occasionally on updating repository owner
279 - fixes #87 RhodeCode crashes occasionally on updating repository owner
267 - fixes #82 broken annotations on files with more than 1 blank line at the end
280 - fixes #82 broken annotations on files with more than 1 blank line at the end
268 - a lot of fixes and tweaks for file browser
281 - a lot of fixes and tweaks for file browser
269 - fixed detached session issues
282 - fixed detached session issues
270 - fixed when user had no repos he would see all repos listed in my account
283 - fixed when user had no repos he would see all repos listed in my account
271 - fixed ui() instance bug when global hgrc settings was loaded for server
284 - fixed ui() instance bug when global hgrc settings was loaded for server
272 instance and all hgrc options were merged with our db ui() object
285 instance and all hgrc options were merged with our db ui() object
273 - numerous small bugfixes
286 - numerous small bugfixes
274
287
275 (special thanks for TkSoh for detailed feedback)
288 (special thanks for TkSoh for detailed feedback)
276
289
277
290
278 1.0.2 (**2010-11-12**)
291 1.0.2 (**2010-11-12**)
279 ======================
292 ======================
280
293
281 news
294 news
282 ----
295 ----
283
296
284 - tested under python2.7
297 - tested under python2.7
285 - bumped sqlalchemy and celery versions
298 - bumped sqlalchemy and celery versions
286
299
287 fixes
300 fixes
288 -----
301 -----
289
302
290 - fixed #59 missing graph.js
303 - fixed #59 missing graph.js
291 - fixed repo_size crash when repository had broken symlinks
304 - fixed repo_size crash when repository had broken symlinks
292 - fixed python2.5 crashes.
305 - fixed python2.5 crashes.
293
306
294
307
295 1.0.1 (**2010-11-10**)
308 1.0.1 (**2010-11-10**)
296 ======================
309 ======================
297
310
298 news
311 news
299 ----
312 ----
300
313
301 - small css updated
314 - small css updated
302
315
303 fixes
316 fixes
304 -----
317 -----
305
318
306 - fixed #53 python2.5 incompatible enumerate calls
319 - fixed #53 python2.5 incompatible enumerate calls
307 - fixed #52 disable mercurial extension for web
320 - fixed #52 disable mercurial extension for web
308 - fixed #51 deleting repositories don't delete it's dependent objects
321 - fixed #51 deleting repositories don't delete it's dependent objects
309
322
310
323
311 1.0.0 (**2010-11-02**)
324 1.0.0 (**2010-11-02**)
312 ======================
325 ======================
313
326
314 - security bugfix simplehg wasn't checking for permissions on commands
327 - security bugfix simplehg wasn't checking for permissions on commands
315 other than pull or push.
328 other than pull or push.
316 - fixed doubled messages after push or pull in admin journal
329 - fixed doubled messages after push or pull in admin journal
317 - templating and css corrections, fixed repo switcher on chrome, updated titles
330 - templating and css corrections, fixed repo switcher on chrome, updated titles
318 - admin menu accessible from options menu on repository view
331 - admin menu accessible from options menu on repository view
319 - permissions cached queries
332 - permissions cached queries
320
333
321 1.0.0rc4 (**2010-10-12**)
334 1.0.0rc4 (**2010-10-12**)
322 ==========================
335 ==========================
323
336
324 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
337 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
325 - removed cache_manager settings from sqlalchemy meta
338 - removed cache_manager settings from sqlalchemy meta
326 - added sqlalchemy cache settings to ini files
339 - added sqlalchemy cache settings to ini files
327 - validated password length and added second try of failure on paster setup-app
340 - validated password length and added second try of failure on paster setup-app
328 - fixed setup database destroy prompt even when there was no db
341 - fixed setup database destroy prompt even when there was no db
329
342
330
343
331 1.0.0rc3 (**2010-10-11**)
344 1.0.0rc3 (**2010-10-11**)
332 =========================
345 =========================
333
346
334 - fixed i18n during installation.
347 - fixed i18n during installation.
335
348
336 1.0.0rc2 (**2010-10-11**)
349 1.0.0rc2 (**2010-10-11**)
337 =========================
350 =========================
338
351
339 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
352 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
340 occure. After vcs is fixed it'll be put back again.
353 occure. After vcs is fixed it'll be put back again.
341 - templating/css rewrites, optimized css. No newline at end of file
354 - templating/css rewrites, optimized css.
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,56 +1,56 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.__init__
3 rhodecode.__init__
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode, a web based repository management based on pylons
6 RhodeCode, a web based repository management based on pylons
7 versioning implementation: http://semver.org/
7 versioning implementation: http://semver.org/
8
8
9 :created_on: Apr 9, 2010
9 :created_on: Apr 9, 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 platform
26 import platform
27
27
28 VERSION = (1, 2, 0, 'beta')
28 VERSION = (1, 3, 0, 'beta')
29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30 __dbversion__ = 3 #defines current db version for migrations
30 __dbversion__ = 4 #defines current db version for migrations
31 __platform__ = platform.system()
31 __platform__ = platform.system()
32 __license__ = 'GPLv3'
32 __license__ = 'GPLv3'
33
33
34 PLATFORM_WIN = ('Windows')
34 PLATFORM_WIN = ('Windows')
35 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
35 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
36
36
37 try:
37 try:
38 from rhodecode.lib.utils import get_current_revision
38 from rhodecode.lib.utils import get_current_revision
39 _rev = get_current_revision()
39 _rev = get_current_revision()
40 except ImportError:
40 except ImportError:
41 #this is needed when doing some setup.py operations
41 #this is needed when doing some setup.py operations
42 _rev = False
42 _rev = False
43
43
44 if len(VERSION) > 3 and _rev:
44 if len(VERSION) > 3 and _rev:
45 __version__ += ' [rev:%s]' % _rev[0]
45 __version__ += ' [rev:%s]' % _rev[0]
46
46
47
47
48 def get_version():
48 def get_version():
49 """Returns shorter version (digit parts only) as string."""
49 """Returns shorter version (digit parts only) as string."""
50
50
51 return '.'.join((str(each) for each in VERSION[:3]))
51 return '.'.join((str(each) for each in VERSION[:3]))
52
52
53 BACKENDS = {
53 BACKENDS = {
54 'hg': 'Mercurial repository',
54 'hg': 'Mercurial repository',
55 #'git': 'Git repository',
55 #'git': 'Git repository',
56 }
56 }
@@ -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,1003 +1,1004 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 cls.query().get( id_ )
129 return cls.query().get( id_ )
129
130
130 @classmethod
131 @classmethod
131 def getAll( cls ):
132 def getAll( cls ):
132 return cls.query().all()
133 return cls.query().all()
133
134
134 @classmethod
135 @classmethod
135 def delete( cls, id_ ):
136 def delete( cls, id_ ):
136 obj = cls.query().get( id_ )
137 obj = cls.query().get( id_ )
137 Session.delete( obj )
138 Session.delete( obj )
138 Session.commit()
139 Session.commit()
139
140
140
141
141 class RhodeCodeSettings( Base, BaseModel ):
142 class RhodeCodeSettings( Base, BaseModel ):
142 __tablename__ = 'rhodecode_settings'
143 __tablename__ = 'rhodecode_settings'
143 __table_args__ = ( UniqueConstraint( 'app_settings_name' ), {'extend_existing':True} )
144 __table_args__ = ( UniqueConstraint( 'app_settings_name' ), {'extend_existing':True} )
144 app_settings_id = Column( "app_settings_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
145 app_settings_id = Column( "app_settings_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
145 app_settings_name = Column( "app_settings_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
146 app_settings_name = Column( "app_settings_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
146 app_settings_value = Column( "app_settings_value", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
147 app_settings_value = Column( "app_settings_value", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
147
148
148 def __init__( self, k = '', v = '' ):
149 def __init__( self, k = '', v = '' ):
149 self.app_settings_name = k
150 self.app_settings_name = k
150 self.app_settings_value = v
151 self.app_settings_value = v
151
152
152 def __repr__( self ):
153 def __repr__( self ):
153 return "<%s('%s:%s')>" % ( self.__class__.__name__,
154 return "<%s('%s:%s')>" % ( self.__class__.__name__,
154 self.app_settings_name, self.app_settings_value )
155 self.app_settings_name, self.app_settings_value )
155
156
156
157
157 @classmethod
158 @classmethod
158 def get_by_name( cls, ldap_key ):
159 def get_by_name( cls, ldap_key ):
159 return cls.query()\
160 return cls.query()\
160 .filter( cls.app_settings_name == ldap_key ).scalar()
161 .filter( cls.app_settings_name == ldap_key ).scalar()
161
162
162 @classmethod
163 @classmethod
163 def get_app_settings( cls, cache = False ):
164 def get_app_settings( cls, cache = False ):
164
165
165 ret = cls.query()
166 ret = cls.query()
166
167
167 if cache:
168 if cache:
168 ret = ret.options( FromCache( "sql_cache_short", "get_hg_settings" ) )
169 ret = ret.options( FromCache( "sql_cache_short", "get_hg_settings" ) )
169
170
170 if not ret:
171 if not ret:
171 raise Exception( 'Could not get application settings !' )
172 raise Exception( 'Could not get application settings !' )
172 settings = {}
173 settings = {}
173 for each in ret:
174 for each in ret:
174 settings['rhodecode_' + each.app_settings_name] = \
175 settings['rhodecode_' + each.app_settings_name] = \
175 each.app_settings_value
176 each.app_settings_value
176
177
177 return settings
178 return settings
178
179
179 @classmethod
180 @classmethod
180 def get_ldap_settings( cls, cache = False ):
181 def get_ldap_settings( cls, cache = False ):
181 ret = cls.query()\
182 ret = cls.query()\
182 .filter( cls.app_settings_name.startswith( 'ldap_' ) )\
183 .filter( cls.app_settings_name.startswith( 'ldap_' ) )\
183 .all()
184 .all()
184 fd = {}
185 fd = {}
185 for row in ret:
186 for row in ret:
186 fd.update( {row.app_settings_name:row.app_settings_value} )
187 fd.update( {row.app_settings_name:row.app_settings_value} )
187
188
188 fd.update( {'ldap_active':str2bool( fd.get( 'ldap_active' ) )} )
189 fd.update( {'ldap_active':str2bool( fd.get( 'ldap_active' ) )} )
189
190
190 return fd
191 return fd
191
192
192
193
193 class RhodeCodeUi( Base, BaseModel ):
194 class RhodeCodeUi( Base, BaseModel ):
194 __tablename__ = 'rhodecode_ui'
195 __tablename__ = 'rhodecode_ui'
195 __table_args__ = ( UniqueConstraint( 'ui_key' ), {'extend_existing':True} )
196 __table_args__ = ( UniqueConstraint( 'ui_key' ), {'extend_existing':True} )
196
197
197 HOOK_UPDATE = 'changegroup.update'
198 HOOK_UPDATE = 'changegroup.update'
198 HOOK_REPO_SIZE = 'changegroup.repo_size'
199 HOOK_REPO_SIZE = 'changegroup.repo_size'
199 HOOK_PUSH = 'pretxnchangegroup.push_logger'
200 HOOK_PUSH = 'pretxnchangegroup.push_logger'
200 HOOK_PULL = 'preoutgoing.pull_logger'
201 HOOK_PULL = 'preoutgoing.pull_logger'
201
202
202 ui_id = Column( "ui_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
203 ui_id = Column( "ui_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
203 ui_section = Column( "ui_section", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
204 ui_section = Column( "ui_section", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
204 ui_key = Column( "ui_key", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
205 ui_key = Column( "ui_key", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
205 ui_value = Column( "ui_value", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
206 ui_value = Column( "ui_value", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
206 ui_active = Column( "ui_active", Boolean(), nullable = True, unique = None, default = True )
207 ui_active = Column( "ui_active", Boolean(), nullable = True, unique = None, default = True )
207
208
208
209
209 @classmethod
210 @classmethod
210 def get_by_key( cls, key ):
211 def get_by_key( cls, key ):
211 return cls.query().filter( cls.ui_key == key )
212 return cls.query().filter( cls.ui_key == key )
212
213
213
214
214 @classmethod
215 @classmethod
215 def get_builtin_hooks( cls ):
216 def get_builtin_hooks( cls ):
216 q = cls.query()
217 q = cls.query()
217 q = q.filter( cls.ui_key.in_( [cls.HOOK_UPDATE,
218 q = q.filter( cls.ui_key.in_( [cls.HOOK_UPDATE,
218 cls.HOOK_REPO_SIZE,
219 cls.HOOK_REPO_SIZE,
219 cls.HOOK_PUSH, cls.HOOK_PULL] ) )
220 cls.HOOK_PUSH, cls.HOOK_PULL] ) )
220 return q.all()
221 return q.all()
221
222
222 @classmethod
223 @classmethod
223 def get_custom_hooks( cls ):
224 def get_custom_hooks( cls ):
224 q = cls.query()
225 q = cls.query()
225 q = q.filter( ~cls.ui_key.in_( [cls.HOOK_UPDATE,
226 q = q.filter( ~cls.ui_key.in_( [cls.HOOK_UPDATE,
226 cls.HOOK_REPO_SIZE,
227 cls.HOOK_REPO_SIZE,
227 cls.HOOK_PUSH, cls.HOOK_PULL] ) )
228 cls.HOOK_PUSH, cls.HOOK_PULL] ) )
228 q = q.filter( cls.ui_section == 'hooks' )
229 q = q.filter( cls.ui_section == 'hooks' )
229 return q.all()
230 return q.all()
230
231
231 @classmethod
232 @classmethod
232 def create_or_update_hook( cls, key, val ):
233 def create_or_update_hook( cls, key, val ):
233 new_ui = cls.get_by_key( key ).scalar() or cls()
234 new_ui = cls.get_by_key( key ).scalar() or cls()
234 new_ui.ui_section = 'hooks'
235 new_ui.ui_section = 'hooks'
235 new_ui.ui_active = True
236 new_ui.ui_active = True
236 new_ui.ui_key = key
237 new_ui.ui_key = key
237 new_ui.ui_value = val
238 new_ui.ui_value = val
238
239
239 Session.add( new_ui )
240 Session.add( new_ui )
240 Session.commit()
241 Session.commit()
241
242
242
243
243 class User( Base, BaseModel ):
244 class User( Base, BaseModel ):
244 __tablename__ = 'users'
245 __tablename__ = 'users'
245 __table_args__ = ( UniqueConstraint( 'username' ), UniqueConstraint( 'email' ), {'extend_existing':True} )
246 __table_args__ = ( UniqueConstraint( 'username' ), UniqueConstraint( 'email' ), {'extend_existing':True} )
246 user_id = Column( "user_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
247 user_id = Column( "user_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
247 username = Column( "username", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
248 username = Column( "username", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
248 password = Column( "password", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
249 password = Column( "password", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
249 active = Column( "active", Boolean(), nullable = True, unique = None, default = None )
250 active = Column( "active", Boolean(), nullable = True, unique = None, default = None )
250 admin = Column( "admin", Boolean(), nullable = True, unique = None, default = False )
251 admin = Column( "admin", Boolean(), nullable = True, unique = None, default = False )
251 name = Column( "name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
252 name = Column( "name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
252 lastname = Column( "lastname", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
253 lastname = Column( "lastname", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
253 email = Column( "email", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
254 email = Column( "email", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
254 last_login = Column( "last_login", DateTime( timezone = False ), nullable = True, unique = None, default = None )
255 last_login = Column( "last_login", DateTime( timezone = False ), nullable = True, unique = None, default = None )
255 ldap_dn = Column( "ldap_dn", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
256 ldap_dn = Column( "ldap_dn", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
256 api_key = Column( "api_key", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
257 api_key = Column( "api_key", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
257
258
258 user_log = relationship( 'UserLog', cascade = 'all' )
259 user_log = relationship( 'UserLog', cascade = 'all' )
259 user_perms = relationship( 'UserToPerm', primaryjoin = "User.user_id==UserToPerm.user_id", cascade = 'all' )
260 user_perms = relationship( 'UserToPerm', primaryjoin = "User.user_id==UserToPerm.user_id", cascade = 'all' )
260
261
261 repositories = relationship( 'Repository' )
262 repositories = relationship( 'Repository' )
262 user_followers = relationship( 'UserFollowing', primaryjoin = 'UserFollowing.follows_user_id==User.user_id', cascade = 'all' )
263 user_followers = relationship( 'UserFollowing', primaryjoin = 'UserFollowing.follows_user_id==User.user_id', cascade = 'all' )
263 repo_to_perm = relationship( 'RepoToPerm', primaryjoin = 'RepoToPerm.user_id==User.user_id', cascade = 'all' )
264 repo_to_perm = relationship( 'RepoToPerm', primaryjoin = 'RepoToPerm.user_id==User.user_id', cascade = 'all' )
264
265
265 group_member = relationship( 'UsersGroupMember', cascade = 'all' )
266 group_member = relationship( 'UsersGroupMember', cascade = 'all' )
266
267
267 @property
268 @property
268 def full_contact( self ):
269 def full_contact( self ):
269 return '%s %s <%s>' % ( self.name, self.lastname, self.email )
270 return '%s %s <%s>' % ( self.name, self.lastname, self.email )
270
271
271 @property
272 @property
272 def short_contact( self ):
273 def short_contact( self ):
273 return '%s %s' % ( self.name, self.lastname )
274 return '%s %s' % ( self.name, self.lastname )
274
275
275 @property
276 @property
276 def is_admin( self ):
277 def is_admin( self ):
277 return self.admin
278 return self.admin
278
279
279 def __repr__( self ):
280 def __repr__( self ):
280 try:
281 try:
281 return "<%s('id:%s:%s')>" % ( self.__class__.__name__,
282 return "<%s('id:%s:%s')>" % ( self.__class__.__name__,
282 self.user_id, self.username )
283 self.user_id, self.username )
283 except:
284 except:
284 return self.__class__.__name__
285 return self.__class__.__name__
285
286
286 @classmethod
287 @classmethod
287 def by_username( cls, username, case_insensitive = False ):
288 def by_username( cls, username, case_insensitive = False ):
288 if case_insensitive:
289 if case_insensitive:
289 return cls.query().filter( cls.username.like( username ) ).one()
290 return cls.query().filter( cls.username.like( username ) ).one()
290 else:
291 else:
291 return cls.query().filter( cls.username == username ).one()
292 return cls.query().filter( cls.username == username ).one()
292
293
293 @classmethod
294 @classmethod
294 def get_by_api_key( cls, api_key ):
295 def get_by_api_key( cls, api_key ):
295 return cls.query().filter( cls.api_key == api_key ).one()
296 return cls.query().filter( cls.api_key == api_key ).one()
296
297
297 def update_lastlogin( self ):
298 def update_lastlogin( self ):
298 """Update user lastlogin"""
299 """Update user lastlogin"""
299
300
300 self.last_login = datetime.datetime.now()
301 self.last_login = datetime.datetime.now()
301 Session.add( self )
302 Session.add( self )
302 Session.commit()
303 Session.commit()
303 log.debug( 'updated user %s lastlogin', self.username )
304 log.debug( 'updated user %s lastlogin', self.username )
304
305
305 @classmethod
306 @classmethod
306 def create( cls, form_data ):
307 def create( cls, form_data ):
307 from rhodecode.lib.auth import get_crypt_password
308 from rhodecode.lib.auth import get_crypt_password
308
309
309 try:
310 try:
310 new_user = cls()
311 new_user = cls()
311 for k, v in form_data.items():
312 for k, v in form_data.items():
312 if k == 'password':
313 if k == 'password':
313 v = get_crypt_password( v )
314 v = get_crypt_password( v )
314 setattr( new_user, k, v )
315 setattr( new_user, k, v )
315
316
316 new_user.api_key = generate_api_key( form_data['username'] )
317 new_user.api_key = generate_api_key( form_data['username'] )
317 Session.add( new_user )
318 Session.add( new_user )
318 Session.commit()
319 Session.commit()
319 return new_user
320 return new_user
320 except:
321 except:
321 log.error( traceback.format_exc() )
322 log.error( traceback.format_exc() )
322 Session.rollback()
323 Session.rollback()
323 raise
324 raise
324
325
325 class UserLog( Base, BaseModel ):
326 class UserLog( Base, BaseModel ):
326 __tablename__ = 'user_logs'
327 __tablename__ = 'user_logs'
327 __table_args__ = {'extend_existing':True}
328 __table_args__ = {'extend_existing':True}
328 user_log_id = Column( "user_log_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
329 user_log_id = Column( "user_log_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
329 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
330 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
330 repository_id = Column( "repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = False, unique = None, default = None )
331 repository_id = Column( "repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = False, unique = None, default = None )
331 repository_name = Column( "repository_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
332 repository_name = Column( "repository_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
332 user_ip = Column( "user_ip", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
333 user_ip = Column( "user_ip", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
333 action = Column( "action", UnicodeText( length = 1200000, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
334 action = Column( "action", UnicodeText( length = 1200000, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
334 action_date = Column( "action_date", DateTime( timezone = False ), nullable = True, unique = None, default = None )
335 action_date = Column( "action_date", DateTime( timezone = False ), nullable = True, unique = None, default = None )
335
336
336 @property
337 @property
337 def action_as_day( self ):
338 def action_as_day( self ):
338 return date( *self.action_date.timetuple()[:3] )
339 return date( *self.action_date.timetuple()[:3] )
339
340
340 user = relationship( 'User' )
341 user = relationship( 'User' )
341 repository = relationship( 'Repository' )
342 repository = relationship( 'Repository' )
342
343
343
344
344 class UsersGroup( Base, BaseModel ):
345 class UsersGroup( Base, BaseModel ):
345 __tablename__ = 'users_groups'
346 __tablename__ = 'users_groups'
346 __table_args__ = {'extend_existing':True}
347 __table_args__ = {'extend_existing':True}
347
348
348 users_group_id = Column( "users_group_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
349 users_group_id = Column( "users_group_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
349 users_group_name = Column( "users_group_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = False, unique = True, default = None )
350 users_group_name = Column( "users_group_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = False, unique = True, default = None )
350 users_group_active = Column( "users_group_active", Boolean(), nullable = True, unique = None, default = None )
351 users_group_active = Column( "users_group_active", Boolean(), nullable = True, unique = None, default = None )
351
352
352 members = relationship( 'UsersGroupMember', cascade = "all, delete, delete-orphan", lazy = "joined" )
353 members = relationship( 'UsersGroupMember', cascade = "all, delete, delete-orphan", lazy = "joined" )
353
354
354 def __repr__( self ):
355 def __repr__( self ):
355 return '<userGroup(%s)>' % ( self.users_group_name )
356 return '<userGroup(%s)>' % ( self.users_group_name )
356
357
357 @classmethod
358 @classmethod
358 def get_by_group_name( cls, group_name, cache = False, case_insensitive = False ):
359 def get_by_group_name( cls, group_name, cache = False, case_insensitive = False ):
359 if case_insensitive:
360 if case_insensitive:
360 gr = cls.query()\
361 gr = cls.query()\
361 .filter( cls.users_group_name.ilike( group_name ) )
362 .filter( cls.users_group_name.ilike( group_name ) )
362 else:
363 else:
363 gr = cls.query()\
364 gr = cls.query()\
364 .filter( cls.users_group_name == group_name )
365 .filter( cls.users_group_name == group_name )
365 if cache:
366 if cache:
366 gr = gr.options( FromCache( "sql_cache_short",
367 gr = gr.options( FromCache( "sql_cache_short",
367 "get_user_%s" % group_name ) )
368 "get_user_%s" % group_name ) )
368 return gr.scalar()
369 return gr.scalar()
369
370
370
371
371 @classmethod
372 @classmethod
372 def get( cls, users_group_id, cache = False ):
373 def get( cls, users_group_id, cache = False ):
373 users_group = cls.query()
374 users_group = cls.query()
374 if cache:
375 if cache:
375 users_group = users_group.options( FromCache( "sql_cache_short",
376 users_group = users_group.options( FromCache( "sql_cache_short",
376 "get_users_group_%s" % users_group_id ) )
377 "get_users_group_%s" % users_group_id ) )
377 return users_group.get( users_group_id )
378 return users_group.get( users_group_id )
378
379
379 @classmethod
380 @classmethod
380 def create( cls, form_data ):
381 def create( cls, form_data ):
381 try:
382 try:
382 new_users_group = cls()
383 new_users_group = cls()
383 for k, v in form_data.items():
384 for k, v in form_data.items():
384 setattr( new_users_group, k, v )
385 setattr( new_users_group, k, v )
385
386
386 Session.add( new_users_group )
387 Session.add( new_users_group )
387 Session.commit()
388 Session.commit()
388 return new_users_group
389 return new_users_group
389 except:
390 except:
390 log.error( traceback.format_exc() )
391 log.error( traceback.format_exc() )
391 Session.rollback()
392 Session.rollback()
392 raise
393 raise
393
394
394 @classmethod
395 @classmethod
395 def update( cls, users_group_id, form_data ):
396 def update( cls, users_group_id, form_data ):
396
397
397 try:
398 try:
398 users_group = cls.get( users_group_id, cache = False )
399 users_group = cls.get( users_group_id, cache = False )
399
400
400 for k, v in form_data.items():
401 for k, v in form_data.items():
401 if k == 'users_group_members':
402 if k == 'users_group_members':
402 users_group.members = []
403 users_group.members = []
403 Session.flush()
404 Session.flush()
404 members_list = []
405 members_list = []
405 if v:
406 if v:
406 for u_id in set( v ):
407 for u_id in set( v ):
407 members_list.append( UsersGroupMember(
408 members_list.append( UsersGroupMember(
408 users_group_id,
409 users_group_id,
409 u_id ) )
410 u_id ) )
410 setattr( users_group, 'members', members_list )
411 setattr( users_group, 'members', members_list )
411 setattr( users_group, k, v )
412 setattr( users_group, k, v )
412
413
413 Session.add( users_group )
414 Session.add( users_group )
414 Session.commit()
415 Session.commit()
415 except:
416 except:
416 log.error( traceback.format_exc() )
417 log.error( traceback.format_exc() )
417 Session.rollback()
418 Session.rollback()
418 raise
419 raise
419
420
420 @classmethod
421 @classmethod
421 def delete( cls, users_group_id ):
422 def delete( cls, users_group_id ):
422 try:
423 try:
423
424
424 # check if this group is not assigned to repo
425 # check if this group is not assigned to repo
425 assigned_groups = UsersGroupRepoToPerm.query()\
426 assigned_groups = UsersGroupRepoToPerm.query()\
426 .filter( UsersGroupRepoToPerm.users_group_id ==
427 .filter( UsersGroupRepoToPerm.users_group_id ==
427 users_group_id ).all()
428 users_group_id ).all()
428
429
429 if assigned_groups:
430 if assigned_groups:
430 raise UsersGroupsAssignedException( 'Group assigned to %s' %
431 raise UsersGroupsAssignedException( 'Group assigned to %s' %
431 assigned_groups )
432 assigned_groups )
432
433
433 users_group = cls.get( users_group_id, cache = False )
434 users_group = cls.get( users_group_id, cache = False )
434 Session.delete( users_group )
435 Session.delete( users_group )
435 Session.commit()
436 Session.commit()
436 except:
437 except:
437 log.error( traceback.format_exc() )
438 log.error( traceback.format_exc() )
438 Session.rollback()
439 Session.rollback()
439 raise
440 raise
440
441
441 class UsersGroupMember( Base, BaseModel ):
442 class UsersGroupMember( Base, BaseModel ):
442 __tablename__ = 'users_groups_members'
443 __tablename__ = 'users_groups_members'
443 __table_args__ = {'extend_existing':True}
444 __table_args__ = {'extend_existing':True}
444
445
445 users_group_member_id = Column( "users_group_member_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
446 users_group_member_id = Column( "users_group_member_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
446 users_group_id = Column( "users_group_id", Integer(), ForeignKey( 'users_groups.users_group_id' ), nullable = False, unique = None, default = None )
447 users_group_id = Column( "users_group_id", Integer(), ForeignKey( 'users_groups.users_group_id' ), nullable = False, unique = None, default = None )
447 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
448 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
448
449
449 user = relationship( 'User', lazy = 'joined' )
450 user = relationship( 'User', lazy = 'joined' )
450 users_group = relationship( 'UsersGroup' )
451 users_group = relationship( 'UsersGroup' )
451
452
452 def __init__( self, gr_id = '', u_id = '' ):
453 def __init__( self, gr_id = '', u_id = '' ):
453 self.users_group_id = gr_id
454 self.users_group_id = gr_id
454 self.user_id = u_id
455 self.user_id = u_id
455
456
456 @staticmethod
457 @staticmethod
457 def add_user_to_group( group, user ):
458 def add_user_to_group( group, user ):
458 ugm = UsersGroupMember()
459 ugm = UsersGroupMember()
459 ugm.users_group = group
460 ugm.users_group = group
460 ugm.user = user
461 ugm.user = user
461 Session.add( ugm )
462 Session.add( ugm )
462 Session.commit()
463 Session.commit()
463 return ugm
464 return ugm
464
465
465 class Repository( Base, BaseModel ):
466 class Repository( Base, BaseModel ):
466 __tablename__ = 'repositories'
467 __tablename__ = 'repositories'
467 __table_args__ = ( UniqueConstraint( 'repo_name' ), {'extend_existing':True}, )
468 __table_args__ = ( UniqueConstraint( 'repo_name' ), {'extend_existing':True}, )
468
469
469 repo_id = Column( "repo_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
470 repo_id = Column( "repo_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
470 repo_name = Column( "repo_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = False, unique = True, default = None )
471 repo_name = Column( "repo_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = False, unique = True, default = None )
471 clone_uri = Column( "clone_uri", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = False, default = None )
472 clone_uri = Column( "clone_uri", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = False, default = None )
472 repo_type = Column( "repo_type", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = False, unique = False, default = 'hg' )
473 repo_type = Column( "repo_type", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = False, unique = False, default = 'hg' )
473 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = False, default = None )
474 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = False, default = None )
474 private = Column( "private", Boolean(), nullable = True, unique = None, default = None )
475 private = Column( "private", Boolean(), nullable = True, unique = None, default = None )
475 enable_statistics = Column( "statistics", Boolean(), nullable = True, unique = None, default = True )
476 enable_statistics = Column( "statistics", Boolean(), nullable = True, unique = None, default = True )
476 enable_downloads = Column( "downloads", Boolean(), nullable = True, unique = None, default = True )
477 enable_downloads = Column( "downloads", Boolean(), nullable = True, unique = None, default = True )
477 description = Column( "description", String( length = 10000, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
478 description = Column( "description", String( length = 10000, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
478 created_on = Column( 'created_on', DateTime( timezone = False ), nullable = True, unique = None, default = datetime.datetime.now )
479 created_on = Column( 'created_on', DateTime( timezone = False ), nullable = True, unique = None, default = datetime.datetime.now )
479
480
480 fork_id = Column( "fork_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = True, unique = False, default = None )
481 fork_id = Column( "fork_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = True, unique = False, default = None )
481 group_id = Column( "group_id", Integer(), ForeignKey( 'groups.group_id' ), nullable = True, unique = False, default = None )
482 group_id = Column( "group_id", Integer(), ForeignKey( 'groups.group_id' ), nullable = True, unique = False, default = None )
482
483
483
484
484 user = relationship( 'User' )
485 user = relationship( 'User' )
485 fork = relationship( 'Repository', remote_side = repo_id )
486 fork = relationship( 'Repository', remote_side = repo_id )
486 group = relationship( 'Group' )
487 group = relationship( 'Group' )
487 repo_to_perm = relationship( 'RepoToPerm', cascade = 'all', order_by = 'RepoToPerm.repo_to_perm_id' )
488 repo_to_perm = relationship( 'RepoToPerm', cascade = 'all', order_by = 'RepoToPerm.repo_to_perm_id' )
488 users_group_to_perm = relationship( 'UsersGroupRepoToPerm', cascade = 'all' )
489 users_group_to_perm = relationship( 'UsersGroupRepoToPerm', cascade = 'all' )
489 stats = relationship( 'Statistics', cascade = 'all', uselist = False )
490 stats = relationship( 'Statistics', cascade = 'all', uselist = False )
490
491
491 followers = relationship( 'UserFollowing', primaryjoin = 'UserFollowing.follows_repo_id==Repository.repo_id', cascade = 'all' )
492 followers = relationship( 'UserFollowing', primaryjoin = 'UserFollowing.follows_repo_id==Repository.repo_id', cascade = 'all' )
492
493
493 logs = relationship( 'UserLog', cascade = 'all' )
494 logs = relationship( 'UserLog', cascade = 'all' )
494
495
495 def __repr__( self ):
496 def __repr__( self ):
496 return "<%s('%s:%s')>" % ( self.__class__.__name__,
497 return "<%s('%s:%s')>" % ( self.__class__.__name__,
497 self.repo_id, self.repo_name )
498 self.repo_id, self.repo_name )
498
499
499 @classmethod
500 @classmethod
500 def by_repo_name( cls, repo_name ):
501 def by_repo_name( cls, repo_name ):
501 q = cls.query().filter( cls.repo_name == repo_name )
502 q = cls.query().filter( cls.repo_name == repo_name )
502
503
503 q = q.options( joinedload( Repository.fork ) )\
504 q = q.options( joinedload( Repository.fork ) )\
504 .options( joinedload( Repository.user ) )\
505 .options( joinedload( Repository.user ) )\
505 .options( joinedload( Repository.group ) )\
506 .options( joinedload( Repository.group ) )\
506
507
507 return q.one()
508 return q.one()
508
509
509 @classmethod
510 @classmethod
510 def get_repo_forks( cls, repo_id ):
511 def get_repo_forks( cls, repo_id ):
511 return cls.query().filter( Repository.fork_id == repo_id )
512 return cls.query().filter( Repository.fork_id == repo_id )
512
513
513 @classmethod
514 @classmethod
514 def base_path( cls ):
515 def base_path( cls ):
515 """
516 """
516 Returns base path when all repos are stored
517 Returns base path when all repos are stored
517
518
518 :param cls:
519 :param cls:
519 """
520 """
520 q = Session.query( RhodeCodeUi ).filter( RhodeCodeUi.ui_key == '/' )
521 q = Session.query( RhodeCodeUi ).filter( RhodeCodeUi.ui_key == '/' )
521 q.options( FromCache( "sql_cache_short", "repository_repo_path" ) )
522 q.options( FromCache( "sql_cache_short", "repository_repo_path" ) )
522 return q.one().ui_value
523 return q.one().ui_value
523
524
524 @property
525 @property
525 def just_name( self ):
526 def just_name( self ):
526 return self.repo_name.split( os.sep )[-1]
527 return self.repo_name.split( os.sep )[-1]
527
528
528 @property
529 @property
529 def groups_with_parents( self ):
530 def groups_with_parents( self ):
530 groups = []
531 groups = []
531 if self.group is None:
532 if self.group is None:
532 return groups
533 return groups
533
534
534 cur_gr = self.group
535 cur_gr = self.group
535 groups.insert( 0, cur_gr )
536 groups.insert( 0, cur_gr )
536 while 1:
537 while 1:
537 gr = getattr( cur_gr, 'parent_group', None )
538 gr = getattr( cur_gr, 'parent_group', None )
538 cur_gr = cur_gr.parent_group
539 cur_gr = cur_gr.parent_group
539 if gr is None:
540 if gr is None:
540 break
541 break
541 groups.insert( 0, gr )
542 groups.insert( 0, gr )
542
543
543 return groups
544 return groups
544
545
545 @property
546 @property
546 def groups_and_repo( self ):
547 def groups_and_repo( self ):
547 return self.groups_with_parents, self.just_name
548 return self.groups_with_parents, self.just_name
548
549
549 @LazyProperty
550 @LazyProperty
550 def repo_path( self ):
551 def repo_path( self ):
551 """
552 """
552 Returns base full path for that repository means where it actually
553 Returns base full path for that repository means where it actually
553 exists on a filesystem
554 exists on a filesystem
554 """
555 """
555 q = RhodeCodeUi.query().filter( RhodeCodeUi.ui_key == '/' )
556 q = RhodeCodeUi.query().filter( RhodeCodeUi.ui_key == '/' )
556 q.options( FromCache( "sql_cache_short", "repository_repo_path" ) )
557 q.options( FromCache( "sql_cache_short", "repository_repo_path" ) )
557 return q.one().ui_value
558 return q.one().ui_value
558
559
559 @property
560 @property
560 def repo_full_path( self ):
561 def repo_full_path( self ):
561 p = [self.repo_path]
562 p = [self.repo_path]
562 # we need to split the name by / since this is how we store the
563 # we need to split the name by / since this is how we store the
563 # names in the database, but that eventually needs to be converted
564 # names in the database, but that eventually needs to be converted
564 # into a valid system path
565 # into a valid system path
565 p += self.repo_name.split( '/' )
566 p += self.repo_name.split( '/' )
566 return os.path.join( *p )
567 return os.path.join( *p )
567
568
568 @property
569 @property
569 def _ui( self ):
570 def _ui( self ):
570 """
571 """
571 Creates an db based ui object for this repository
572 Creates an db based ui object for this repository
572 """
573 """
573 from mercurial import ui
574 from mercurial import ui
574 from mercurial import config
575 from mercurial import config
575 baseui = ui.ui()
576 baseui = ui.ui()
576
577
577 #clean the baseui object
578 #clean the baseui object
578 baseui._ocfg = config.config()
579 baseui._ocfg = config.config()
579 baseui._ucfg = config.config()
580 baseui._ucfg = config.config()
580 baseui._tcfg = config.config()
581 baseui._tcfg = config.config()
581
582
582
583
583 ret = RhodeCodeUi.query()\
584 ret = RhodeCodeUi.query()\
584 .options( FromCache( "sql_cache_short", "repository_repo_ui" ) ).all()
585 .options( FromCache( "sql_cache_short", "repository_repo_ui" ) ).all()
585
586
586 hg_ui = ret
587 hg_ui = ret
587 for ui_ in hg_ui:
588 for ui_ in hg_ui:
588 if ui_.ui_active:
589 if ui_.ui_active:
589 log.debug( 'settings ui from db[%s]%s:%s', ui_.ui_section,
590 log.debug( 'settings ui from db[%s]%s:%s', ui_.ui_section,
590 ui_.ui_key, ui_.ui_value )
591 ui_.ui_key, ui_.ui_value )
591 baseui.setconfig( ui_.ui_section, ui_.ui_key, ui_.ui_value )
592 baseui.setconfig( ui_.ui_section, ui_.ui_key, ui_.ui_value )
592
593
593 return baseui
594 return baseui
594
595
595 @classmethod
596 @classmethod
596 def is_valid( cls, repo_name ):
597 def is_valid( cls, repo_name ):
597 """
598 """
598 returns True if given repo name is a valid filesystem repository
599 returns True if given repo name is a valid filesystem repository
599
600
600 @param cls:
601 @param cls:
601 @param repo_name:
602 @param repo_name:
602 """
603 """
603 from rhodecode.lib.utils import is_valid_repo
604 from rhodecode.lib.utils import is_valid_repo
604
605
605 return is_valid_repo( repo_name, cls.base_path() )
606 return is_valid_repo( repo_name, cls.base_path() )
606
607
607
608
608 #==========================================================================
609 #==========================================================================
609 # SCM PROPERTIES
610 # SCM PROPERTIES
610 #==========================================================================
611 #==========================================================================
611
612
612 def get_changeset( self, rev ):
613 def get_changeset( self, rev ):
613 return get_changeset_safe( self.scm_instance, rev )
614 return get_changeset_safe( self.scm_instance, rev )
614
615
615 @property
616 @property
616 def tip( self ):
617 def tip( self ):
617 return self.get_changeset( 'tip' )
618 return self.get_changeset( 'tip' )
618
619
619 @property
620 @property
620 def author( self ):
621 def author( self ):
621 return self.tip.author
622 return self.tip.author
622
623
623 @property
624 @property
624 def last_change( self ):
625 def last_change( self ):
625 return self.scm_instance.last_change
626 return self.scm_instance.last_change
626
627
627 #==========================================================================
628 #==========================================================================
628 # SCM CACHE INSTANCE
629 # SCM CACHE INSTANCE
629 #==========================================================================
630 #==========================================================================
630
631
631 @property
632 @property
632 def invalidate( self ):
633 def invalidate( self ):
633 """
634 """
634 Returns Invalidation object if this repo should be invalidated
635 Returns Invalidation object if this repo should be invalidated
635 None otherwise. `cache_active = False` means that this cache
636 None otherwise. `cache_active = False` means that this cache
636 state is not valid and needs to be invalidated
637 state is not valid and needs to be invalidated
637 """
638 """
638 return CacheInvalidation.query()\
639 return CacheInvalidation.query()\
639 .filter( CacheInvalidation.cache_key == self.repo_name )\
640 .filter( CacheInvalidation.cache_key == self.repo_name )\
640 .filter( CacheInvalidation.cache_active == False )\
641 .filter( CacheInvalidation.cache_active == False )\
641 .scalar()
642 .scalar()
642
643
643 def set_invalidate( self ):
644 def set_invalidate( self ):
644 """
645 """
645 set a cache for invalidation for this instance
646 set a cache for invalidation for this instance
646 """
647 """
647 inv = CacheInvalidation.query()\
648 inv = CacheInvalidation.query()\
648 .filter( CacheInvalidation.cache_key == self.repo_name )\
649 .filter( CacheInvalidation.cache_key == self.repo_name )\
649 .scalar()
650 .scalar()
650
651
651 if inv is None:
652 if inv is None:
652 inv = CacheInvalidation( self.repo_name )
653 inv = CacheInvalidation( self.repo_name )
653 inv.cache_active = True
654 inv.cache_active = True
654 Session.add( inv )
655 Session.add( inv )
655 Session.commit()
656 Session.commit()
656
657
657 @LazyProperty
658 @LazyProperty
658 def scm_instance( self ):
659 def scm_instance( self ):
659 return self.__get_instance()
660 return self.__get_instance()
660
661
661 @property
662 @property
662 def scm_instance_cached( self ):
663 def scm_instance_cached( self ):
663 @cache_region( 'long_term' )
664 @cache_region( 'long_term' )
664 def _c( repo_name ):
665 def _c( repo_name ):
665 return self.__get_instance()
666 return self.__get_instance()
666
667
667 # TODO: remove this trick when beaker 1.6 is released
668 # TODO: remove this trick when beaker 1.6 is released
668 # and have fixed this issue with not supporting unicode keys
669 # and have fixed this issue with not supporting unicode keys
669 rn = safe_str( self.repo_name )
670 rn = safe_str( self.repo_name )
670
671
671 inv = self.invalidate
672 inv = self.invalidate
672 if inv is not None:
673 if inv is not None:
673 region_invalidate( _c, None, rn )
674 region_invalidate( _c, None, rn )
674 # update our cache
675 # update our cache
675 inv.cache_active = True
676 inv.cache_active = True
676 Session.add( inv )
677 Session.add( inv )
677 Session.commit()
678 Session.commit()
678
679
679 return _c( rn )
680 return _c( rn )
680
681
681 def __get_instance( self ):
682 def __get_instance( self ):
682
683
683 repo_full_path = self.repo_full_path
684 repo_full_path = self.repo_full_path
684
685
685 try:
686 try:
686 alias = get_scm( repo_full_path )[0]
687 alias = get_scm( repo_full_path )[0]
687 log.debug( 'Creating instance of %s repository', alias )
688 log.debug( 'Creating instance of %s repository', alias )
688 backend = get_backend( alias )
689 backend = get_backend( alias )
689 except VCSError:
690 except VCSError:
690 log.error( traceback.format_exc() )
691 log.error( traceback.format_exc() )
691 log.error( 'Perhaps this repository is in db and not in '
692 log.error( 'Perhaps this repository is in db and not in '
692 'filesystem run rescan repositories with '
693 'filesystem run rescan repositories with '
693 '"destroy old data " option from admin panel' )
694 '"destroy old data " option from admin panel' )
694 return
695 return
695
696
696 if alias == 'hg':
697 if alias == 'hg':
697
698
698 repo = backend( safe_str( repo_full_path ), create = False,
699 repo = backend( safe_str( repo_full_path ), create = False,
699 baseui = self._ui )
700 baseui = self._ui )
700 #skip hidden web repository
701 #skip hidden web repository
701 if repo._get_hidden():
702 if repo._get_hidden():
702 return
703 return
703 else:
704 else:
704 repo = backend( repo_full_path, create = False )
705 repo = backend( repo_full_path, create = False )
705
706
706 return repo
707 return repo
707
708
708
709
709 class Group( Base, BaseModel ):
710 class Group( Base, BaseModel ):
710 __tablename__ = 'groups'
711 __tablename__ = 'groups'
711 __table_args__ = ( UniqueConstraint( 'group_name', 'group_parent_id' ),
712 __table_args__ = ( UniqueConstraint( 'group_name', 'group_parent_id' ),
712 CheckConstraint( 'group_id != group_parent_id' ), {'extend_existing':True}, )
713 CheckConstraint( 'group_id != group_parent_id' ), {'extend_existing':True}, )
713 __mapper_args__ = {'order_by':'group_name'}
714 __mapper_args__ = {'order_by':'group_name'}
714
715
715 group_id = Column( "group_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
716 group_id = Column( "group_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
716 group_name = Column( "group_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = False, unique = True, default = None )
717 group_name = Column( "group_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = False, unique = True, default = None )
717 group_parent_id = Column( "group_parent_id", Integer(), ForeignKey( 'groups.group_id' ), nullable = True, unique = None, default = None )
718 group_parent_id = Column( "group_parent_id", Integer(), ForeignKey( 'groups.group_id' ), nullable = True, unique = None, default = None )
718 group_description = Column( "group_description", String( length = 10000, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
719 group_description = Column( "group_description", String( length = 10000, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
719
720
720 parent_group = relationship( 'Group', remote_side = group_id )
721 parent_group = relationship( 'Group', remote_side = group_id )
721
722
722
723
723 def __init__( self, group_name = '', parent_group = None ):
724 def __init__( self, group_name = '', parent_group = None ):
724 self.group_name = group_name
725 self.group_name = group_name
725 self.parent_group = parent_group
726 self.parent_group = parent_group
726
727
727 def __repr__( self ):
728 def __repr__( self ):
728 return "<%s('%s:%s')>" % ( self.__class__.__name__, self.group_id,
729 return "<%s('%s:%s')>" % ( self.__class__.__name__, self.group_id,
729 self.group_name )
730 self.group_name )
730
731
731 @classmethod
732 @classmethod
732 def url_sep( cls ):
733 def url_sep( cls ):
733 return '/'
734 return '/'
734
735
735 @classmethod
736 @classmethod
736 def get_by_group_name( cls, group_name, cache = False, case_insensitive = False ):
737 def get_by_group_name( cls, group_name, cache = False, case_insensitive = False ):
737 if case_insensitive:
738 if case_insensitive:
738 gr = cls.query()\
739 gr = cls.query()\
739 .filter( cls.group_name.ilike( group_name ) )
740 .filter( cls.group_name.ilike( group_name ) )
740 else:
741 else:
741 gr = cls.query()\
742 gr = cls.query()\
742 .filter( cls.group_name == group_name )
743 .filter( cls.group_name == group_name )
743 if cache:
744 if cache:
744 gr = gr.options( FromCache( "sql_cache_short",
745 gr = gr.options( FromCache( "sql_cache_short",
745 "get_group_%s" % group_name ) )
746 "get_group_%s" % group_name ) )
746 return gr.scalar()
747 return gr.scalar()
747
748
748 @property
749 @property
749 def parents( self ):
750 def parents( self ):
750 parents_recursion_limit = 5
751 parents_recursion_limit = 5
751 groups = []
752 groups = []
752 if self.parent_group is None:
753 if self.parent_group is None:
753 return groups
754 return groups
754 cur_gr = self.parent_group
755 cur_gr = self.parent_group
755 groups.insert( 0, cur_gr )
756 groups.insert( 0, cur_gr )
756 cnt = 0
757 cnt = 0
757 while 1:
758 while 1:
758 cnt += 1
759 cnt += 1
759 gr = getattr( cur_gr, 'parent_group', None )
760 gr = getattr( cur_gr, 'parent_group', None )
760 cur_gr = cur_gr.parent_group
761 cur_gr = cur_gr.parent_group
761 if gr is None:
762 if gr is None:
762 break
763 break
763 if cnt == parents_recursion_limit:
764 if cnt == parents_recursion_limit:
764 # this will prevent accidental infinit loops
765 # this will prevent accidental infinit loops
765 log.error( 'group nested more than %s' %
766 log.error( 'group nested more than %s' %
766 parents_recursion_limit )
767 parents_recursion_limit )
767 break
768 break
768
769
769 groups.insert( 0, gr )
770 groups.insert( 0, gr )
770 return groups
771 return groups
771
772
772 @property
773 @property
773 def children( self ):
774 def children( self ):
774 return Group.query().filter( Group.parent_group == self )
775 return Group.query().filter( Group.parent_group == self )
775
776
776 @property
777 @property
777 def full_path( self ):
778 def full_path( self ):
778 return Group.url_sep().join( [g.group_name for g in self.parents] +
779 return Group.url_sep().join( [g.group_name for g in self.parents] +
779 [self.group_name] )
780 [self.group_name] )
780
781
781 @property
782 @property
782 def repositories( self ):
783 def repositories( self ):
783 return Repository.query().filter( Repository.group == self )
784 return Repository.query().filter( Repository.group == self )
784
785
785 @property
786 @property
786 def repositories_recursive_count( self ):
787 def repositories_recursive_count( self ):
787 cnt = self.repositories.count()
788 cnt = self.repositories.count()
788
789
789 def children_count( group ):
790 def children_count( group ):
790 cnt = 0
791 cnt = 0
791 for child in group.children:
792 for child in group.children:
792 cnt += child.repositories.count()
793 cnt += child.repositories.count()
793 cnt += children_count( child )
794 cnt += children_count( child )
794 return cnt
795 return cnt
795
796
796 return cnt + children_count( self )
797 return cnt + children_count( self )
797
798
798 class Permission( Base, BaseModel ):
799 class Permission( Base, BaseModel ):
799 __tablename__ = 'permissions'
800 __tablename__ = 'permissions'
800 __table_args__ = {'extend_existing':True}
801 __table_args__ = {'extend_existing':True}
801 permission_id = Column( "permission_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
802 permission_id = Column( "permission_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
802 permission_name = Column( "permission_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
803 permission_name = Column( "permission_name", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
803 permission_longname = Column( "permission_longname", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
804 permission_longname = Column( "permission_longname", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
804
805
805 def __repr__( self ):
806 def __repr__( self ):
806 return "<%s('%s:%s')>" % ( self.__class__.__name__,
807 return "<%s('%s:%s')>" % ( self.__class__.__name__,
807 self.permission_id, self.permission_name )
808 self.permission_id, self.permission_name )
808
809
809 @classmethod
810 @classmethod
810 def get_by_key( cls, key ):
811 def get_by_key( cls, key ):
811 return cls.query().filter( cls.permission_name == key ).scalar()
812 return cls.query().filter( cls.permission_name == key ).scalar()
812
813
813 class RepoToPerm( Base, BaseModel ):
814 class RepoToPerm( Base, BaseModel ):
814 __tablename__ = 'repo_to_perm'
815 __tablename__ = 'repo_to_perm'
815 __table_args__ = ( UniqueConstraint( 'user_id', 'repository_id' ), {'extend_existing':True} )
816 __table_args__ = ( UniqueConstraint( 'user_id', 'repository_id' ), {'extend_existing':True} )
816 repo_to_perm_id = Column( "repo_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
817 repo_to_perm_id = Column( "repo_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
817 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
818 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
818 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
819 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
819 repository_id = Column( "repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = False, unique = None, default = None )
820 repository_id = Column( "repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = False, unique = None, default = None )
820
821
821 user = relationship( 'User' )
822 user = relationship( 'User' )
822 permission = relationship( 'Permission' )
823 permission = relationship( 'Permission' )
823 repository = relationship( 'Repository' )
824 repository = relationship( 'Repository' )
824
825
825 class UserToPerm( Base, BaseModel ):
826 class UserToPerm( Base, BaseModel ):
826 __tablename__ = 'user_to_perm'
827 __tablename__ = 'user_to_perm'
827 __table_args__ = ( UniqueConstraint( 'user_id', 'permission_id' ), {'extend_existing':True} )
828 __table_args__ = ( UniqueConstraint( 'user_id', 'permission_id' ), {'extend_existing':True} )
828 user_to_perm_id = Column( "user_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
829 user_to_perm_id = Column( "user_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
829 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
830 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
830 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
831 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
831
832
832 user = relationship( 'User' )
833 user = relationship( 'User' )
833 permission = relationship( 'Permission' )
834 permission = relationship( 'Permission' )
834
835
835 @classmethod
836 @classmethod
836 def has_perm( cls, user_id, perm ):
837 def has_perm( cls, user_id, perm ):
837 if not isinstance( perm, Permission ):
838 if not isinstance( perm, Permission ):
838 raise Exception( 'perm needs to be an instance of Permission class' )
839 raise Exception( 'perm needs to be an instance of Permission class' )
839
840
840 return cls.query().filter( cls.user_id == user_id )\
841 return cls.query().filter( cls.user_id == user_id )\
841 .filter( cls.permission == perm ).scalar() is not None
842 .filter( cls.permission == perm ).scalar() is not None
842
843
843 @classmethod
844 @classmethod
844 def grant_perm( cls, user_id, perm ):
845 def grant_perm( cls, user_id, perm ):
845 if not isinstance( perm, Permission ):
846 if not isinstance( perm, Permission ):
846 raise Exception( 'perm needs to be an instance of Permission class' )
847 raise Exception( 'perm needs to be an instance of Permission class' )
847
848
848 new = cls()
849 new = cls()
849 new.user_id = user_id
850 new.user_id = user_id
850 new.permission = perm
851 new.permission = perm
851 try:
852 try:
852 Session.add( new )
853 Session.add( new )
853 Session.commit()
854 Session.commit()
854 except:
855 except:
855 Session.rollback()
856 Session.rollback()
856
857
857
858
858 @classmethod
859 @classmethod
859 def revoke_perm( cls, user_id, perm ):
860 def revoke_perm( cls, user_id, perm ):
860 if not isinstance( perm, Permission ):
861 if not isinstance( perm, Permission ):
861 raise Exception( 'perm needs to be an instance of Permission class' )
862 raise Exception( 'perm needs to be an instance of Permission class' )
862
863
863 try:
864 try:
864 cls.query().filter( cls.user_id == user_id )\
865 cls.query().filter( cls.user_id == user_id )\
865 .filter( cls.permission == perm ).delete()
866 .filter( cls.permission == perm ).delete()
866 Session.commit()
867 Session.commit()
867 except:
868 except:
868 Session.rollback()
869 Session.rollback()
869
870
870 class UsersGroupRepoToPerm( Base, BaseModel ):
871 class UsersGroupRepoToPerm( Base, BaseModel ):
871 __tablename__ = 'users_group_repo_to_perm'
872 __tablename__ = 'users_group_repo_to_perm'
872 __table_args__ = ( UniqueConstraint( 'repository_id', 'users_group_id', 'permission_id' ), {'extend_existing':True} )
873 __table_args__ = ( UniqueConstraint( 'repository_id', 'users_group_id', 'permission_id' ), {'extend_existing':True} )
873 users_group_to_perm_id = Column( "users_group_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
874 users_group_to_perm_id = Column( "users_group_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
874 users_group_id = Column( "users_group_id", Integer(), ForeignKey( 'users_groups.users_group_id' ), nullable = False, unique = None, default = None )
875 users_group_id = Column( "users_group_id", Integer(), ForeignKey( 'users_groups.users_group_id' ), nullable = False, unique = None, default = None )
875 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
876 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
876 repository_id = Column( "repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = False, unique = None, default = None )
877 repository_id = Column( "repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = False, unique = None, default = None )
877
878
878 users_group = relationship( 'UsersGroup' )
879 users_group = relationship( 'UsersGroup' )
879 permission = relationship( 'Permission' )
880 permission = relationship( 'Permission' )
880 repository = relationship( 'Repository' )
881 repository = relationship( 'Repository' )
881
882
882 def __repr__( self ):
883 def __repr__( self ):
883 return '<userGroup:%s => %s >' % ( self.users_group, self.repository )
884 return '<userGroup:%s => %s >' % ( self.users_group, self.repository )
884
885
885 class UsersGroupToPerm( Base, BaseModel ):
886 class UsersGroupToPerm( Base, BaseModel ):
886 __tablename__ = 'users_group_to_perm'
887 __tablename__ = 'users_group_to_perm'
887 users_group_to_perm_id = Column( "users_group_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
888 users_group_to_perm_id = Column( "users_group_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
888 users_group_id = Column( "users_group_id", Integer(), ForeignKey( 'users_groups.users_group_id' ), nullable = False, unique = None, default = None )
889 users_group_id = Column( "users_group_id", Integer(), ForeignKey( 'users_groups.users_group_id' ), nullable = False, unique = None, default = None )
889 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
890 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
890
891
891 users_group = relationship( 'UsersGroup' )
892 users_group = relationship( 'UsersGroup' )
892 permission = relationship( 'Permission' )
893 permission = relationship( 'Permission' )
893
894
894
895
895 @classmethod
896 @classmethod
896 def has_perm( cls, users_group_id, perm ):
897 def has_perm( cls, users_group_id, perm ):
897 if not isinstance( perm, Permission ):
898 if not isinstance( perm, Permission ):
898 raise Exception( 'perm needs to be an instance of Permission class' )
899 raise Exception( 'perm needs to be an instance of Permission class' )
899
900
900 return cls.query().filter( cls.users_group_id ==
901 return cls.query().filter( cls.users_group_id ==
901 users_group_id )\
902 users_group_id )\
902 .filter( cls.permission == perm )\
903 .filter( cls.permission == perm )\
903 .scalar() is not None
904 .scalar() is not None
904
905
905 @classmethod
906 @classmethod
906 def grant_perm( cls, users_group_id, perm ):
907 def grant_perm( cls, users_group_id, perm ):
907 if not isinstance( perm, Permission ):
908 if not isinstance( perm, Permission ):
908 raise Exception( 'perm needs to be an instance of Permission class' )
909 raise Exception( 'perm needs to be an instance of Permission class' )
909
910
910 new = cls()
911 new = cls()
911 new.users_group_id = users_group_id
912 new.users_group_id = users_group_id
912 new.permission = perm
913 new.permission = perm
913 try:
914 try:
914 Session.add( new )
915 Session.add( new )
915 Session.commit()
916 Session.commit()
916 except:
917 except:
917 Session.rollback()
918 Session.rollback()
918
919
919
920
920 @classmethod
921 @classmethod
921 def revoke_perm( cls, users_group_id, perm ):
922 def revoke_perm( cls, users_group_id, perm ):
922 if not isinstance( perm, Permission ):
923 if not isinstance( perm, Permission ):
923 raise Exception( 'perm needs to be an instance of Permission class' )
924 raise Exception( 'perm needs to be an instance of Permission class' )
924
925
925 try:
926 try:
926 cls.query().filter( cls.users_group_id == users_group_id )\
927 cls.query().filter( cls.users_group_id == users_group_id )\
927 .filter( cls.permission == perm ).delete()
928 .filter( cls.permission == perm ).delete()
928 Session.commit()
929 Session.commit()
929 except:
930 except:
930 Session.rollback()
931 Session.rollback()
931
932
932
933
933 class GroupToPerm( Base, BaseModel ):
934 class GroupToPerm( Base, BaseModel ):
934 __tablename__ = 'group_to_perm'
935 __tablename__ = 'group_to_perm'
935 __table_args__ = ( UniqueConstraint( 'group_id', 'permission_id' ), {'extend_existing':True} )
936 __table_args__ = ( UniqueConstraint( 'group_id', 'permission_id' ), {'extend_existing':True} )
936
937
937 group_to_perm_id = Column( "group_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
938 group_to_perm_id = Column( "group_to_perm_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
938 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
939 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
939 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
940 permission_id = Column( "permission_id", Integer(), ForeignKey( 'permissions.permission_id' ), nullable = False, unique = None, default = None )
940 group_id = Column( "group_id", Integer(), ForeignKey( 'groups.group_id' ), nullable = False, unique = None, default = None )
941 group_id = Column( "group_id", Integer(), ForeignKey( 'groups.group_id' ), nullable = False, unique = None, default = None )
941
942
942 user = relationship( 'User' )
943 user = relationship( 'User' )
943 permission = relationship( 'Permission' )
944 permission = relationship( 'Permission' )
944 group = relationship( 'Group' )
945 group = relationship( 'Group' )
945
946
946 class Statistics( Base, BaseModel ):
947 class Statistics( Base, BaseModel ):
947 __tablename__ = 'statistics'
948 __tablename__ = 'statistics'
948 __table_args__ = ( UniqueConstraint( 'repository_id' ), {'extend_existing':True} )
949 __table_args__ = ( UniqueConstraint( 'repository_id' ), {'extend_existing':True} )
949 stat_id = Column( "stat_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
950 stat_id = Column( "stat_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
950 repository_id = Column( "repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = False, unique = True, default = None )
951 repository_id = Column( "repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = False, unique = True, default = None )
951 stat_on_revision = Column( "stat_on_revision", Integer(), nullable = False )
952 stat_on_revision = Column( "stat_on_revision", Integer(), nullable = False )
952 commit_activity = Column( "commit_activity", LargeBinary( 1000000 ), nullable = False )#JSON data
953 commit_activity = Column( "commit_activity", LargeBinary( 1000000 ), nullable = False )#JSON data
953 commit_activity_combined = Column( "commit_activity_combined", LargeBinary(), nullable = False )#JSON data
954 commit_activity_combined = Column( "commit_activity_combined", LargeBinary(), nullable = False )#JSON data
954 languages = Column( "languages", LargeBinary( 1000000 ), nullable = False )#JSON data
955 languages = Column( "languages", LargeBinary( 1000000 ), nullable = False )#JSON data
955
956
956 repository = relationship( 'Repository', single_parent = True )
957 repository = relationship( 'Repository', single_parent = True )
957
958
958 class UserFollowing( Base, BaseModel ):
959 class UserFollowing( Base, BaseModel ):
959 __tablename__ = 'user_followings'
960 __tablename__ = 'user_followings'
960 __table_args__ = ( UniqueConstraint( 'user_id', 'follows_repository_id' ),
961 __table_args__ = ( UniqueConstraint( 'user_id', 'follows_repository_id' ),
961 UniqueConstraint( 'user_id', 'follows_user_id' )
962 UniqueConstraint( 'user_id', 'follows_user_id' )
962 , {'extend_existing':True} )
963 , {'extend_existing':True} )
963
964
964 user_following_id = Column( "user_following_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
965 user_following_id = Column( "user_following_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
965 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
966 user_id = Column( "user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = False, unique = None, default = None )
966 follows_repo_id = Column( "follows_repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = True, unique = None, default = None )
967 follows_repo_id = Column( "follows_repository_id", Integer(), ForeignKey( 'repositories.repo_id' ), nullable = True, unique = None, default = None )
967 follows_user_id = Column( "follows_user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = True, unique = None, default = None )
968 follows_user_id = Column( "follows_user_id", Integer(), ForeignKey( 'users.user_id' ), nullable = True, unique = None, default = None )
968 follows_from = Column( 'follows_from', DateTime( timezone = False ), nullable = True, unique = None, default = datetime.datetime.now )
969 follows_from = Column( 'follows_from', DateTime( timezone = False ), nullable = True, unique = None, default = datetime.datetime.now )
969
970
970 user = relationship( 'User', primaryjoin = 'User.user_id==UserFollowing.user_id' )
971 user = relationship( 'User', primaryjoin = 'User.user_id==UserFollowing.user_id' )
971
972
972 follows_user = relationship( 'User', primaryjoin = 'User.user_id==UserFollowing.follows_user_id' )
973 follows_user = relationship( 'User', primaryjoin = 'User.user_id==UserFollowing.follows_user_id' )
973 follows_repository = relationship( 'Repository', order_by = 'Repository.repo_name' )
974 follows_repository = relationship( 'Repository', order_by = 'Repository.repo_name' )
974
975
975
976
976 @classmethod
977 @classmethod
977 def get_repo_followers( cls, repo_id ):
978 def get_repo_followers( cls, repo_id ):
978 return cls.query().filter( cls.follows_repo_id == repo_id )
979 return cls.query().filter( cls.follows_repo_id == repo_id )
979
980
980 class CacheInvalidation( Base, BaseModel ):
981 class CacheInvalidation( Base, BaseModel ):
981 __tablename__ = 'cache_invalidation'
982 __tablename__ = 'cache_invalidation'
982 __table_args__ = ( UniqueConstraint( 'cache_key' ), {'extend_existing':True} )
983 __table_args__ = ( UniqueConstraint( 'cache_key' ), {'extend_existing':True} )
983 cache_id = Column( "cache_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
984 cache_id = Column( "cache_id", Integer(), nullable = False, unique = True, default = None, primary_key = True )
984 cache_key = Column( "cache_key", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
985 cache_key = Column( "cache_key", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
985 cache_args = Column( "cache_args", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
986 cache_args = Column( "cache_args", String( length = 255, convert_unicode = False, assert_unicode = None ), nullable = True, unique = None, default = None )
986 cache_active = Column( "cache_active", Boolean(), nullable = True, unique = None, default = False )
987 cache_active = Column( "cache_active", Boolean(), nullable = True, unique = None, default = False )
987
988
988
989
989 def __init__( self, cache_key, cache_args = '' ):
990 def __init__( self, cache_key, cache_args = '' ):
990 self.cache_key = cache_key
991 self.cache_key = cache_key
991 self.cache_args = cache_args
992 self.cache_args = cache_args
992 self.cache_active = False
993 self.cache_active = False
993
994
994 def __repr__( self ):
995 def __repr__( self ):
995 return "<%s('%s:%s')>" % ( self.__class__.__name__,
996 return "<%s('%s:%s')>" % ( self.__class__.__name__,
996 self.cache_id, self.cache_key )
997 self.cache_id, self.cache_key )
997
998
998 class DbMigrateVersion( Base, BaseModel ):
999 class DbMigrateVersion( Base, BaseModel ):
999 __tablename__ = 'db_migrate_version'
1000 __tablename__ = 'db_migrate_version'
1000 __table_args__ = {'extend_existing':True}
1001 __table_args__ = {'extend_existing':True}
1001 repository_id = Column( 'repository_id', String( 250 ), primary_key = True )
1002 repository_id = Column( 'repository_id', String( 250 ), primary_key = True )
1002 repository_path = Column( 'repository_path', Text )
1003 repository_path = Column( 'repository_path', Text )
1003 version = Column( 'version', Integer )
1004 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,358 +1,361 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 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 os
25 import os
26 import shutil
26 import shutil
27 import logging
27 import logging
28 import traceback
28 import traceback
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from sqlalchemy.orm import joinedload, make_transient
31 from sqlalchemy.orm import joinedload, make_transient
32
32
33 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
34 from vcs.backends import get_backend
34 from vcs.backends import get_backend
35
35
36 from rhodecode.lib import safe_str
36 from rhodecode.lib import safe_str
37
37
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.caching_query import FromCache
39 from rhodecode.model.caching_query import FromCache
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class RepoModel(BaseModel):
47 class RepoModel(BaseModel):
48
48
49 @LazyProperty
49 @LazyProperty
50 def repos_path(self):
50 def repos_path(self):
51 """Get's the repositories root path from database
51 """Get's the repositories root path from database
52 """
52 """
53
53
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
55 return q.ui_value
55 return q.ui_value
56
56
57 def get(self, repo_id, cache=False):
57 def get(self, repo_id, cache=False):
58 repo = self.sa.query(Repository)\
58 repo = self.sa.query(Repository)\
59 .filter(Repository.repo_id == repo_id)
59 .filter(Repository.repo_id == repo_id)
60
60
61 if cache:
61 if cache:
62 repo = repo.options(FromCache("sql_cache_short",
62 repo = repo.options(FromCache("sql_cache_short",
63 "get_repo_%s" % repo_id))
63 "get_repo_%s" % repo_id))
64 return repo.scalar()
64 return repo.scalar()
65
65
66 def get_by_repo_name(self, repo_name, cache=False):
66 def get_by_repo_name(self, repo_name, cache=False):
67 repo = self.sa.query(Repository)\
67 repo = self.sa.query(Repository)\
68 .filter(Repository.repo_name == repo_name)
68 .filter(Repository.repo_name == repo_name)
69
69
70 if cache:
70 if cache:
71 repo = repo.options(FromCache("sql_cache_short",
71 repo = repo.options(FromCache("sql_cache_short",
72 "get_repo_%s" % repo_name))
72 "get_repo_%s" % repo_name))
73 return repo.scalar()
73 return repo.scalar()
74
74
75
75
76 def get_users_js(self):
76 def get_users_js(self):
77
77
78 users = self.sa.query(User).filter(User.active == True).all()
78 users = self.sa.query(User).filter(User.active == True).all()
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
81 u.lastname, u.username)
81 u.lastname, u.username)
82 for u in users])
82 for u in users])
83 return users_array
83 return users_array
84
84
85 def get_users_groups_js(self):
85 def get_users_groups_js(self):
86 users_groups = self.sa.query(UsersGroup)\
86 users_groups = self.sa.query(UsersGroup)\
87 .filter(UsersGroup.users_group_active == True).all()
87 .filter(UsersGroup.users_group_active == True).all()
88
88
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
90
90
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
92 (gr.users_group_id, gr.users_group_name,
92 (gr.users_group_id, gr.users_group_name,
93 len(gr.members))
93 len(gr.members))
94 for gr in users_groups])
94 for gr in users_groups])
95 return users_groups_array
95 return users_groups_array
96
96
97 def update(self, repo_name, form_data):
97 def update(self, repo_name, form_data):
98 try:
98 try:
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
100
100
101 #update permissions
101 #update permissions
102 for member, perm, member_type in form_data['perms_updates']:
102 for member, perm, member_type in form_data['perms_updates']:
103 if member_type == 'user':
103 if member_type == 'user':
104 r2p = self.sa.query(RepoToPerm)\
104 r2p = self.sa.query(RepoToPerm)\
105 .filter(RepoToPerm.user == User.by_username(member))\
105 .filter(RepoToPerm.user == User.by_username(member))\
106 .filter(RepoToPerm.repository == cur_repo)\
106 .filter(RepoToPerm.repository == cur_repo)\
107 .one()
107 .one()
108
108
109 r2p.permission = self.sa.query(Permission)\
109 r2p.permission = self.sa.query(Permission)\
110 .filter(Permission.permission_name ==
110 .filter(Permission.permission_name ==
111 perm).scalar()
111 perm).scalar()
112 self.sa.add(r2p)
112 self.sa.add(r2p)
113 else:
113 else:
114 g2p = self.sa.query(UsersGroupRepoToPerm)\
114 g2p = self.sa.query(UsersGroupRepoToPerm)\
115 .filter(UsersGroupRepoToPerm.users_group ==
115 .filter(UsersGroupRepoToPerm.users_group ==
116 UsersGroup.get_by_group_name(member))\
116 UsersGroup.get_by_group_name(member))\
117 .filter(UsersGroupRepoToPerm.repository ==
117 .filter(UsersGroupRepoToPerm.repository ==
118 cur_repo).one()
118 cur_repo).one()
119
119
120 g2p.permission = self.sa.query(Permission)\
120 g2p.permission = self.sa.query(Permission)\
121 .filter(Permission.permission_name ==
121 .filter(Permission.permission_name ==
122 perm).scalar()
122 perm).scalar()
123 self.sa.add(g2p)
123 self.sa.add(g2p)
124
124
125 #set new permissions
125 #set new permissions
126 for member, perm, member_type in form_data['perms_new']:
126 for member, perm, member_type in form_data['perms_new']:
127 if member_type == 'user':
127 if member_type == 'user':
128 r2p = RepoToPerm()
128 r2p = RepoToPerm()
129 r2p.repository = cur_repo
129 r2p.repository = cur_repo
130 r2p.user = User.by_username(member)
130 r2p.user = User.by_username(member)
131
131
132 r2p.permission = self.sa.query(Permission)\
132 r2p.permission = self.sa.query(Permission)\
133 .filter(Permission.
133 .filter(Permission.
134 permission_name == perm)\
134 permission_name == perm)\
135 .scalar()
135 .scalar()
136 self.sa.add(r2p)
136 self.sa.add(r2p)
137 else:
137 else:
138 g2p = UsersGroupRepoToPerm()
138 g2p = UsersGroupRepoToPerm()
139 g2p.repository = cur_repo
139 g2p.repository = cur_repo
140 g2p.users_group = UsersGroup.get_by_group_name(member)
140 g2p.users_group = UsersGroup.get_by_group_name(member)
141 g2p.permission = self.sa.query(Permission)\
141 g2p.permission = self.sa.query(Permission)\
142 .filter(Permission.
142 .filter(Permission.
143 permission_name == perm)\
143 permission_name == perm)\
144 .scalar()
144 .scalar()
145 self.sa.add(g2p)
145 self.sa.add(g2p)
146
146
147 #update current repo
147 #update current repo
148 for k, v in form_data.items():
148 for k, v in form_data.items():
149 if k == 'user':
149 if k == 'user':
150 cur_repo.user = User.by_username(v)
150 cur_repo.user = User.by_username(v)
151 elif k == 'repo_name':
151 elif k == 'repo_name':
152 cur_repo.repo_name = form_data['repo_name_full']
152 cur_repo.repo_name = form_data['repo_name_full']
153 elif k == 'repo_group':
153 elif k == 'repo_group':
154 cur_repo.group_id = v
154 cur_repo.group_id = v
155
155
156 else:
156 else:
157 setattr(cur_repo, k, v)
157 setattr(cur_repo, k, v)
158
158
159 self.sa.add(cur_repo)
159 self.sa.add(cur_repo)
160
160
161 if repo_name != form_data['repo_name_full']:
161 if repo_name != form_data['repo_name_full']:
162 # rename repository
162 # rename repository
163 self.__rename_repo(old=repo_name,
163 self.__rename_repo(old=repo_name,
164 new=form_data['repo_name_full'])
164 new=form_data['repo_name_full'])
165
165
166 self.sa.commit()
166 self.sa.commit()
167 except:
167 except:
168 log.error(traceback.format_exc())
168 log.error(traceback.format_exc())
169 self.sa.rollback()
169 self.sa.rollback()
170 raise
170 raise
171
171
172 def create(self, form_data, cur_user, just_db=False, fork=False):
172 def create(self, form_data, cur_user, just_db=False, fork=False):
173
173
174 try:
174 try:
175 if fork:
175 if fork:
176 repo_name = form_data['fork_name']
176 repo_name = form_data['fork_name']
177 org_name = form_data['repo_name']
177 org_name = form_data['repo_name']
178 org_full_name = org_name
178 org_full_name = org_name
179
179
180 else:
180 else:
181 org_name = repo_name = form_data['repo_name']
181 org_name = repo_name = form_data['repo_name']
182 repo_name_full = form_data['repo_name_full']
182 repo_name_full = form_data['repo_name_full']
183
183
184 new_repo = Repository()
184 new_repo = Repository()
185 new_repo.enable_statistics = False
185 new_repo.enable_statistics = False
186 for k, v in form_data.items():
186 for k, v in form_data.items():
187 if k == 'repo_name':
187 if k == 'repo_name':
188 if fork:
188 if fork:
189 v = repo_name
189 v = repo_name
190 else:
190 else:
191 v = repo_name_full
191 v = repo_name_full
192 if k == 'repo_group':
192 if k == 'repo_group':
193 k = 'group_id'
193 k = 'group_id'
194
194
195 if k == 'description':
196 v = v or repo_name
197
195 setattr(new_repo, k, v)
198 setattr(new_repo, k, v)
196
199
197 if fork:
200 if fork:
198 parent_repo = self.sa.query(Repository)\
201 parent_repo = self.sa.query(Repository)\
199 .filter(Repository.repo_name == org_full_name).one()
202 .filter(Repository.repo_name == org_full_name).one()
200 new_repo.fork = parent_repo
203 new_repo.fork = parent_repo
201
204
202 new_repo.user_id = cur_user.user_id
205 new_repo.user_id = cur_user.user_id
203 self.sa.add(new_repo)
206 self.sa.add(new_repo)
204
207
205 #create default permission
208 #create default permission
206 repo_to_perm = RepoToPerm()
209 repo_to_perm = RepoToPerm()
207 default = 'repository.read'
210 default = 'repository.read'
208 for p in UserModel(self.sa).get_by_username('default',
211 for p in UserModel(self.sa).get_by_username('default',
209 cache=False).user_perms:
212 cache=False).user_perms:
210 if p.permission.permission_name.startswith('repository.'):
213 if p.permission.permission_name.startswith('repository.'):
211 default = p.permission.permission_name
214 default = p.permission.permission_name
212 break
215 break
213
216
214 default_perm = 'repository.none' if form_data['private'] else default
217 default_perm = 'repository.none' if form_data['private'] else default
215
218
216 repo_to_perm.permission_id = self.sa.query(Permission)\
219 repo_to_perm.permission_id = self.sa.query(Permission)\
217 .filter(Permission.permission_name == default_perm)\
220 .filter(Permission.permission_name == default_perm)\
218 .one().permission_id
221 .one().permission_id
219
222
220 repo_to_perm.repository = new_repo
223 repo_to_perm.repository = new_repo
221 repo_to_perm.user_id = UserModel(self.sa)\
224 repo_to_perm.user_id = UserModel(self.sa)\
222 .get_by_username('default', cache=False).user_id
225 .get_by_username('default', cache=False).user_id
223
226
224 self.sa.add(repo_to_perm)
227 self.sa.add(repo_to_perm)
225
228
226 if not just_db:
229 if not just_db:
227 self.__create_repo(repo_name, form_data['repo_type'],
230 self.__create_repo(repo_name, form_data['repo_type'],
228 form_data['repo_group'],
231 form_data['repo_group'],
229 form_data['clone_uri'])
232 form_data['clone_uri'])
230
233
231 self.sa.commit()
234 self.sa.commit()
232
235
233 #now automatically start following this repository as owner
236 #now automatically start following this repository as owner
234 from rhodecode.model.scm import ScmModel
237 from rhodecode.model.scm import ScmModel
235 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
238 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
236 cur_user.user_id)
239 cur_user.user_id)
237
240
238 except:
241 except:
239 log.error(traceback.format_exc())
242 log.error(traceback.format_exc())
240 self.sa.rollback()
243 self.sa.rollback()
241 raise
244 raise
242
245
243 def create_fork(self, form_data, cur_user):
246 def create_fork(self, form_data, cur_user):
244 from rhodecode.lib.celerylib import tasks, run_task
247 from rhodecode.lib.celerylib import tasks, run_task
245 run_task(tasks.create_repo_fork, form_data, cur_user)
248 run_task(tasks.create_repo_fork, form_data, cur_user)
246
249
247 def delete(self, repo):
250 def delete(self, repo):
248 try:
251 try:
249 self.sa.delete(repo)
252 self.sa.delete(repo)
250 self.__delete_repo(repo)
253 self.__delete_repo(repo)
251 self.sa.commit()
254 self.sa.commit()
252 except:
255 except:
253 log.error(traceback.format_exc())
256 log.error(traceback.format_exc())
254 self.sa.rollback()
257 self.sa.rollback()
255 raise
258 raise
256
259
257 def delete_perm_user(self, form_data, repo_name):
260 def delete_perm_user(self, form_data, repo_name):
258 try:
261 try:
259 self.sa.query(RepoToPerm)\
262 self.sa.query(RepoToPerm)\
260 .filter(RepoToPerm.repository \
263 .filter(RepoToPerm.repository \
261 == self.get_by_repo_name(repo_name))\
264 == self.get_by_repo_name(repo_name))\
262 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
265 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
263 self.sa.commit()
266 self.sa.commit()
264 except:
267 except:
265 log.error(traceback.format_exc())
268 log.error(traceback.format_exc())
266 self.sa.rollback()
269 self.sa.rollback()
267 raise
270 raise
268
271
269 def delete_perm_users_group(self, form_data, repo_name):
272 def delete_perm_users_group(self, form_data, repo_name):
270 try:
273 try:
271 self.sa.query(UsersGroupRepoToPerm)\
274 self.sa.query(UsersGroupRepoToPerm)\
272 .filter(UsersGroupRepoToPerm.repository \
275 .filter(UsersGroupRepoToPerm.repository \
273 == self.get_by_repo_name(repo_name))\
276 == self.get_by_repo_name(repo_name))\
274 .filter(UsersGroupRepoToPerm.users_group_id \
277 .filter(UsersGroupRepoToPerm.users_group_id \
275 == form_data['users_group_id']).delete()
278 == form_data['users_group_id']).delete()
276 self.sa.commit()
279 self.sa.commit()
277 except:
280 except:
278 log.error(traceback.format_exc())
281 log.error(traceback.format_exc())
279 self.sa.rollback()
282 self.sa.rollback()
280 raise
283 raise
281
284
282 def delete_stats(self, repo_name):
285 def delete_stats(self, repo_name):
283 try:
286 try:
284 self.sa.query(Statistics)\
287 self.sa.query(Statistics)\
285 .filter(Statistics.repository == \
288 .filter(Statistics.repository == \
286 self.get_by_repo_name(repo_name)).delete()
289 self.get_by_repo_name(repo_name)).delete()
287 self.sa.commit()
290 self.sa.commit()
288 except:
291 except:
289 log.error(traceback.format_exc())
292 log.error(traceback.format_exc())
290 self.sa.rollback()
293 self.sa.rollback()
291 raise
294 raise
292
295
293 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
296 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
294 """
297 """
295 makes repository on filesystem. It's group aware means it'll create
298 makes repository on filesystem. It's group aware means it'll create
296 a repository within a group, and alter the paths accordingly of
299 a repository within a group, and alter the paths accordingly of
297 group location
300 group location
298
301
299 :param repo_name:
302 :param repo_name:
300 :param alias:
303 :param alias:
301 :param parent_id:
304 :param parent_id:
302 :param clone_uri:
305 :param clone_uri:
303 """
306 """
304 from rhodecode.lib.utils import is_valid_repo
307 from rhodecode.lib.utils import is_valid_repo
305
308
306 if new_parent_id:
309 if new_parent_id:
307 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
310 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
308 new_parent_path = os.sep.join(paths)
311 new_parent_path = os.sep.join(paths)
309 else:
312 else:
310 new_parent_path = ''
313 new_parent_path = ''
311
314
312 repo_path = os.path.join(*map(lambda x:safe_str(x),
315 repo_path = os.path.join(*map(lambda x:safe_str(x),
313 [self.repos_path, new_parent_path, repo_name]))
316 [self.repos_path, new_parent_path, repo_name]))
314
317
315 if is_valid_repo(repo_path, self.repos_path) is False:
318 if is_valid_repo(repo_path, self.repos_path) is False:
316 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
319 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
317 clone_uri)
320 clone_uri)
318 backend = get_backend(alias)
321 backend = get_backend(alias)
319
322
320 backend(repo_path, create=True, src_url=clone_uri)
323 backend(repo_path, create=True, src_url=clone_uri)
321
324
322
325
323 def __rename_repo(self, old, new):
326 def __rename_repo(self, old, new):
324 """
327 """
325 renames repository on filesystem
328 renames repository on filesystem
326
329
327 :param old: old name
330 :param old: old name
328 :param new: new name
331 :param new: new name
329 """
332 """
330 log.info('renaming repo from %s to %s', old, new)
333 log.info('renaming repo from %s to %s', old, new)
331
334
332 old_path = os.path.join(self.repos_path, old)
335 old_path = os.path.join(self.repos_path, old)
333 new_path = os.path.join(self.repos_path, new)
336 new_path = os.path.join(self.repos_path, new)
334 if os.path.isdir(new_path):
337 if os.path.isdir(new_path):
335 raise Exception('Was trying to rename to already existing dir %s' \
338 raise Exception('Was trying to rename to already existing dir %s' \
336 % new_path)
339 % new_path)
337 shutil.move(old_path, new_path)
340 shutil.move(old_path, new_path)
338
341
339 def __delete_repo(self, repo):
342 def __delete_repo(self, repo):
340 """
343 """
341 removes repo from filesystem, the removal is acctually made by
344 removes repo from filesystem, the removal is acctually made by
342 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
345 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
343 repository is no longer valid for rhodecode, can be undeleted later on
346 repository is no longer valid for rhodecode, can be undeleted later on
344 by reverting the renames on this repository
347 by reverting the renames on this repository
345
348
346 :param repo: repo object
349 :param repo: repo object
347 """
350 """
348 rm_path = os.path.join(self.repos_path, repo.repo_name)
351 rm_path = os.path.join(self.repos_path, repo.repo_name)
349 log.info("Removing %s", rm_path)
352 log.info("Removing %s", rm_path)
350 #disable hg/git
353 #disable hg/git
351 alias = repo.repo_type
354 alias = repo.repo_type
352 shutil.move(os.path.join(rm_path, '.%s' % alias),
355 shutil.move(os.path.join(rm_path, '.%s' % alias),
353 os.path.join(rm_path, 'rm__.%s' % alias))
356 os.path.join(rm_path, 'rm__.%s' % alias))
354 #disable repo
357 #disable repo
355 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
358 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
356 % (datetime.today()\
359 % (datetime.today()\
357 .strftime('%Y%m%d_%H%M%S_%f'),
360 .strftime('%Y%m%d_%H%M%S_%f'),
358 repo.repo_name)))
361 repo.repo_name)))
@@ -1,387 +1,388 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 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 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from rhodecode.lib import safe_unicode
31 from rhodecode.model import BaseModel
32 from rhodecode.model import BaseModel
32 from rhodecode.model.caching_query import FromCache
33 from rhodecode.model.caching_query import FromCache
33 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
34 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
34 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
35 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
35 from rhodecode.lib.exceptions import DefaultUserException, \
36 from rhodecode.lib.exceptions import DefaultUserException, \
36 UserOwnsReposException
37 UserOwnsReposException
37
38
38 from sqlalchemy.exc import DatabaseError
39 from sqlalchemy.exc import DatabaseError
39 from rhodecode.lib import generate_api_key
40 from rhodecode.lib import generate_api_key
40 from sqlalchemy.orm import joinedload
41 from sqlalchemy.orm import joinedload
41
42
42 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
43
44
44 PERM_WEIGHTS = {'repository.none': 0,
45 PERM_WEIGHTS = {'repository.none': 0,
45 'repository.read': 1,
46 'repository.read': 1,
46 'repository.write': 3,
47 'repository.write': 3,
47 'repository.admin': 3}
48 'repository.admin': 3}
48
49
49
50
50 class UserModel(BaseModel):
51 class UserModel(BaseModel):
51 def get(self, user_id, cache=False):
52 def get(self, user_id, cache=False):
52 user = self.sa.query(User)
53 user = self.sa.query(User)
53 if cache:
54 if cache:
54 user = user.options(FromCache("sql_cache_short",
55 user = user.options(FromCache("sql_cache_short",
55 "get_user_%s" % user_id))
56 "get_user_%s" % user_id))
56 return user.get(user_id)
57 return user.get(user_id)
57
58
58 def get_by_username(self, username, cache=False, case_insensitive=False):
59 def get_by_username(self, username, cache=False, case_insensitive=False):
59
60
60 if case_insensitive:
61 if case_insensitive:
61 user = self.sa.query(User).filter(User.username.ilike(username))
62 user = self.sa.query(User).filter(User.username.ilike(username))
62 else:
63 else:
63 user = self.sa.query(User)\
64 user = self.sa.query(User)\
64 .filter(User.username == username)
65 .filter(User.username == username)
65 if cache:
66 if cache:
66 user = user.options(FromCache("sql_cache_short",
67 user = user.options(FromCache("sql_cache_short",
67 "get_user_%s" % username))
68 "get_user_%s" % username))
68 return user.scalar()
69 return user.scalar()
69
70
70 def get_by_api_key(self, api_key, cache=False):
71 def get_by_api_key(self, api_key, cache=False):
71
72
72 user = self.sa.query(User)\
73 user = self.sa.query(User)\
73 .filter(User.api_key == api_key)
74 .filter(User.api_key == api_key)
74 if cache:
75 if cache:
75 user = user.options(FromCache("sql_cache_short",
76 user = user.options(FromCache("sql_cache_short",
76 "get_user_%s" % api_key))
77 "get_user_%s" % api_key))
77 return user.scalar()
78 return user.scalar()
78
79
79 def create(self, form_data):
80 def create(self, form_data):
80 try:
81 try:
81 new_user = User()
82 new_user = User()
82 for k, v in form_data.items():
83 for k, v in form_data.items():
83 setattr(new_user, k, v)
84 setattr(new_user, k, v)
84
85
85 new_user.api_key = generate_api_key(form_data['username'])
86 new_user.api_key = generate_api_key(form_data['username'])
86 self.sa.add(new_user)
87 self.sa.add(new_user)
87 self.sa.commit()
88 self.sa.commit()
88 return new_user
89 return new_user
89 except:
90 except:
90 log.error(traceback.format_exc())
91 log.error(traceback.format_exc())
91 self.sa.rollback()
92 self.sa.rollback()
92 raise
93 raise
93
94
94 def create_ldap(self, username, password, user_dn, attrs):
95 def create_ldap(self, username, password, user_dn, attrs):
95 """
96 """
96 Checks if user is in database, if not creates this user marked
97 Checks if user is in database, if not creates this user marked
97 as ldap user
98 as ldap user
98 :param username:
99 :param username:
99 :param password:
100 :param password:
100 :param user_dn:
101 :param user_dn:
101 :param attrs:
102 :param attrs:
102 """
103 """
103 from rhodecode.lib.auth import get_crypt_password
104 from rhodecode.lib.auth import get_crypt_password
104 log.debug('Checking for such ldap account in RhodeCode database')
105 log.debug('Checking for such ldap account in RhodeCode database')
105 if self.get_by_username(username, case_insensitive=True) is None:
106 if self.get_by_username(username, case_insensitive=True) is None:
106 try:
107 try:
107 new_user = User()
108 new_user = User()
108 # add ldap account always lowercase
109 # add ldap account always lowercase
109 new_user.username = username.lower()
110 new_user.username = username.lower()
110 new_user.password = get_crypt_password(password)
111 new_user.password = get_crypt_password(password)
111 new_user.api_key = generate_api_key(username)
112 new_user.api_key = generate_api_key(username)
112 new_user.email = attrs['email']
113 new_user.email = attrs['email']
113 new_user.active = True
114 new_user.active = True
114 new_user.ldap_dn = user_dn
115 new_user.ldap_dn = safe_unicode(user_dn)
115 new_user.name = attrs['name']
116 new_user.name = attrs['name']
116 new_user.lastname = attrs['lastname']
117 new_user.lastname = attrs['lastname']
117
118
118 self.sa.add(new_user)
119 self.sa.add(new_user)
119 self.sa.commit()
120 self.sa.commit()
120 return True
121 return True
121 except (DatabaseError,):
122 except (DatabaseError,):
122 log.error(traceback.format_exc())
123 log.error(traceback.format_exc())
123 self.sa.rollback()
124 self.sa.rollback()
124 raise
125 raise
125 log.debug('this %s user exists skipping creation of ldap account',
126 log.debug('this %s user exists skipping creation of ldap account',
126 username)
127 username)
127 return False
128 return False
128
129
129 def create_registration(self, form_data):
130 def create_registration(self, form_data):
130 from rhodecode.lib.celerylib import tasks, run_task
131 from rhodecode.lib.celerylib import tasks, run_task
131 try:
132 try:
132 new_user = User()
133 new_user = User()
133 for k, v in form_data.items():
134 for k, v in form_data.items():
134 if k != 'admin':
135 if k != 'admin':
135 setattr(new_user, k, v)
136 setattr(new_user, k, v)
136
137
137 self.sa.add(new_user)
138 self.sa.add(new_user)
138 self.sa.commit()
139 self.sa.commit()
139 body = ('New user registration\n'
140 body = ('New user registration\n'
140 'username: %s\n'
141 'username: %s\n'
141 'email: %s\n')
142 'email: %s\n')
142 body = body % (form_data['username'], form_data['email'])
143 body = body % (form_data['username'], form_data['email'])
143
144
144 run_task(tasks.send_email, None,
145 run_task(tasks.send_email, None,
145 _('[RhodeCode] New User registration'),
146 _('[RhodeCode] New User registration'),
146 body)
147 body)
147 except:
148 except:
148 log.error(traceback.format_exc())
149 log.error(traceback.format_exc())
149 self.sa.rollback()
150 self.sa.rollback()
150 raise
151 raise
151
152
152 def update(self, user_id, form_data):
153 def update(self, user_id, form_data):
153 try:
154 try:
154 user = self.get(user_id, cache=False)
155 user = self.get(user_id, cache=False)
155 if user.username == 'default':
156 if user.username == 'default':
156 raise DefaultUserException(
157 raise DefaultUserException(
157 _("You can't Edit this user since it's"
158 _("You can't Edit this user since it's"
158 " crucial for entire application"))
159 " crucial for entire application"))
159
160
160 for k, v in form_data.items():
161 for k, v in form_data.items():
161 if k == 'new_password' and v != '':
162 if k == 'new_password' and v != '':
162 user.password = v
163 user.password = v
163 user.api_key = generate_api_key(user.username)
164 user.api_key = generate_api_key(user.username)
164 else:
165 else:
165 setattr(user, k, v)
166 setattr(user, k, v)
166
167
167 self.sa.add(user)
168 self.sa.add(user)
168 self.sa.commit()
169 self.sa.commit()
169 except:
170 except:
170 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
171 self.sa.rollback()
172 self.sa.rollback()
172 raise
173 raise
173
174
174 def update_my_account(self, user_id, form_data):
175 def update_my_account(self, user_id, form_data):
175 try:
176 try:
176 user = self.get(user_id, cache=False)
177 user = self.get(user_id, cache=False)
177 if user.username == 'default':
178 if user.username == 'default':
178 raise DefaultUserException(
179 raise DefaultUserException(
179 _("You can't Edit this user since it's"
180 _("You can't Edit this user since it's"
180 " crucial for entire application"))
181 " crucial for entire application"))
181 for k, v in form_data.items():
182 for k, v in form_data.items():
182 if k == 'new_password' and v != '':
183 if k == 'new_password' and v != '':
183 user.password = v
184 user.password = v
184 user.api_key = generate_api_key(user.username)
185 user.api_key = generate_api_key(user.username)
185 else:
186 else:
186 if k not in ['admin', 'active']:
187 if k not in ['admin', 'active']:
187 setattr(user, k, v)
188 setattr(user, k, v)
188
189
189 self.sa.add(user)
190 self.sa.add(user)
190 self.sa.commit()
191 self.sa.commit()
191 except:
192 except:
192 log.error(traceback.format_exc())
193 log.error(traceback.format_exc())
193 self.sa.rollback()
194 self.sa.rollback()
194 raise
195 raise
195
196
196 def delete(self, user_id):
197 def delete(self, user_id):
197 try:
198 try:
198 user = self.get(user_id, cache=False)
199 user = self.get(user_id, cache=False)
199 if user.username == 'default':
200 if user.username == 'default':
200 raise DefaultUserException(
201 raise DefaultUserException(
201 _("You can't remove this user since it's"
202 _("You can't remove this user since it's"
202 " crucial for entire application"))
203 " crucial for entire application"))
203 if user.repositories:
204 if user.repositories:
204 raise UserOwnsReposException(_('This user still owns %s '
205 raise UserOwnsReposException(_('This user still owns %s '
205 'repositories and cannot be '
206 'repositories and cannot be '
206 'removed. Switch owners or '
207 'removed. Switch owners or '
207 'remove those repositories') \
208 'remove those repositories') \
208 % user.repositories)
209 % user.repositories)
209 self.sa.delete(user)
210 self.sa.delete(user)
210 self.sa.commit()
211 self.sa.commit()
211 except:
212 except:
212 log.error(traceback.format_exc())
213 log.error(traceback.format_exc())
213 self.sa.rollback()
214 self.sa.rollback()
214 raise
215 raise
215
216
216 def reset_password_link(self, data):
217 def reset_password_link(self, data):
217 from rhodecode.lib.celerylib import tasks, run_task
218 from rhodecode.lib.celerylib import tasks, run_task
218 run_task(tasks.send_password_link, data['email'])
219 run_task(tasks.send_password_link, data['email'])
219
220
220 def reset_password(self, data):
221 def reset_password(self, data):
221 from rhodecode.lib.celerylib import tasks, run_task
222 from rhodecode.lib.celerylib import tasks, run_task
222 run_task(tasks.reset_user_password, data['email'])
223 run_task(tasks.reset_user_password, data['email'])
223
224
224 def fill_data(self, auth_user, user_id=None, api_key=None):
225 def fill_data(self, auth_user, user_id=None, api_key=None):
225 """
226 """
226 Fetches auth_user by user_id,or api_key if present.
227 Fetches auth_user by user_id,or api_key if present.
227 Fills auth_user attributes with those taken from database.
228 Fills auth_user attributes with those taken from database.
228 Additionally set's is_authenitated if lookup fails
229 Additionally set's is_authenitated if lookup fails
229 present in database
230 present in database
230
231
231 :param auth_user: instance of user to set attributes
232 :param auth_user: instance of user to set attributes
232 :param user_id: user id to fetch by
233 :param user_id: user id to fetch by
233 :param api_key: api key to fetch by
234 :param api_key: api key to fetch by
234 """
235 """
235 if user_id is None and api_key is None:
236 if user_id is None and api_key is None:
236 raise Exception('You need to pass user_id or api_key')
237 raise Exception('You need to pass user_id or api_key')
237
238
238 try:
239 try:
239 if api_key:
240 if api_key:
240 dbuser = self.get_by_api_key(api_key)
241 dbuser = self.get_by_api_key(api_key)
241 else:
242 else:
242 dbuser = self.get(user_id)
243 dbuser = self.get(user_id)
243
244
244 if dbuser is not None:
245 if dbuser is not None:
245 log.debug('filling %s data', dbuser)
246 log.debug('filling %s data', dbuser)
246 for k, v in dbuser.get_dict().items():
247 for k, v in dbuser.get_dict().items():
247 setattr(auth_user, k, v)
248 setattr(auth_user, k, v)
248
249
249 except:
250 except:
250 log.error(traceback.format_exc())
251 log.error(traceback.format_exc())
251 auth_user.is_authenticated = False
252 auth_user.is_authenticated = False
252
253
253 return auth_user
254 return auth_user
254
255
255 def fill_perms(self, user):
256 def fill_perms(self, user):
256 """
257 """
257 Fills user permission attribute with permissions taken from database
258 Fills user permission attribute with permissions taken from database
258 works for permissions given for repositories, and for permissions that
259 works for permissions given for repositories, and for permissions that
259 are granted to groups
260 are granted to groups
260
261
261 :param user: user instance to fill his perms
262 :param user: user instance to fill his perms
262 """
263 """
263
264
264 user.permissions['repositories'] = {}
265 user.permissions['repositories'] = {}
265 user.permissions['global'] = set()
266 user.permissions['global'] = set()
266
267
267 #======================================================================
268 #======================================================================
268 # fetch default permissions
269 # fetch default permissions
269 #======================================================================
270 #======================================================================
270 default_user = self.get_by_username('default', cache=True)
271 default_user = self.get_by_username('default', cache=True)
271
272
272 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
273 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
273 .join((Repository, RepoToPerm.repository_id ==
274 .join((Repository, RepoToPerm.repository_id ==
274 Repository.repo_id))\
275 Repository.repo_id))\
275 .join((Permission, RepoToPerm.permission_id ==
276 .join((Permission, RepoToPerm.permission_id ==
276 Permission.permission_id))\
277 Permission.permission_id))\
277 .filter(RepoToPerm.user == default_user).all()
278 .filter(RepoToPerm.user == default_user).all()
278
279
279 if user.is_admin:
280 if user.is_admin:
280 #==================================================================
281 #==================================================================
281 # #admin have all default rights set to admin
282 # #admin have all default rights set to admin
282 #==================================================================
283 #==================================================================
283 user.permissions['global'].add('hg.admin')
284 user.permissions['global'].add('hg.admin')
284
285
285 for perm in default_perms:
286 for perm in default_perms:
286 p = 'repository.admin'
287 p = 'repository.admin'
287 user.permissions['repositories'][perm.RepoToPerm.
288 user.permissions['repositories'][perm.RepoToPerm.
288 repository.repo_name] = p
289 repository.repo_name] = p
289
290
290 else:
291 else:
291 #==================================================================
292 #==================================================================
292 # set default permissions
293 # set default permissions
293 #==================================================================
294 #==================================================================
294 uid = user.user_id
295 uid = user.user_id
295
296
296 #default global
297 #default global
297 default_global_perms = self.sa.query(UserToPerm)\
298 default_global_perms = self.sa.query(UserToPerm)\
298 .filter(UserToPerm.user == default_user)
299 .filter(UserToPerm.user == default_user)
299
300
300 for perm in default_global_perms:
301 for perm in default_global_perms:
301 user.permissions['global'].add(perm.permission.permission_name)
302 user.permissions['global'].add(perm.permission.permission_name)
302
303
303 #default for repositories
304 #default for repositories
304 for perm in default_perms:
305 for perm in default_perms:
305 if perm.Repository.private and not (perm.Repository.user_id ==
306 if perm.Repository.private and not (perm.Repository.user_id ==
306 uid):
307 uid):
307 #diself.sable defaults for private repos,
308 #diself.sable defaults for private repos,
308 p = 'repository.none'
309 p = 'repository.none'
309 elif perm.Repository.user_id == uid:
310 elif perm.Repository.user_id == uid:
310 #set admin if owner
311 #set admin if owner
311 p = 'repository.admin'
312 p = 'repository.admin'
312 else:
313 else:
313 p = perm.Permission.permission_name
314 p = perm.Permission.permission_name
314
315
315 user.permissions['repositories'][perm.RepoToPerm.
316 user.permissions['repositories'][perm.RepoToPerm.
316 repository.repo_name] = p
317 repository.repo_name] = p
317
318
318 #==================================================================
319 #==================================================================
319 # overwrite default with user permissions if any
320 # overwrite default with user permissions if any
320 #==================================================================
321 #==================================================================
321
322
322 #user global
323 #user global
323 user_perms = self.sa.query(UserToPerm)\
324 user_perms = self.sa.query(UserToPerm)\
324 .options(joinedload(UserToPerm.permission))\
325 .options(joinedload(UserToPerm.permission))\
325 .filter(UserToPerm.user_id == uid).all()
326 .filter(UserToPerm.user_id == uid).all()
326
327
327 for perm in user_perms:
328 for perm in user_perms:
328 user.permissions['global'].add(perm.permission.
329 user.permissions['global'].add(perm.permission.
329 permission_name)
330 permission_name)
330
331
331 #user repositories
332 #user repositories
332 user_repo_perms = self.sa.query(RepoToPerm, Permission,
333 user_repo_perms = self.sa.query(RepoToPerm, Permission,
333 Repository)\
334 Repository)\
334 .join((Repository, RepoToPerm.repository_id ==
335 .join((Repository, RepoToPerm.repository_id ==
335 Repository.repo_id))\
336 Repository.repo_id))\
336 .join((Permission, RepoToPerm.permission_id ==
337 .join((Permission, RepoToPerm.permission_id ==
337 Permission.permission_id))\
338 Permission.permission_id))\
338 .filter(RepoToPerm.user_id == uid).all()
339 .filter(RepoToPerm.user_id == uid).all()
339
340
340 for perm in user_repo_perms:
341 for perm in user_repo_perms:
341 # set admin if owner
342 # set admin if owner
342 if perm.Repository.user_id == uid:
343 if perm.Repository.user_id == uid:
343 p = 'repository.admin'
344 p = 'repository.admin'
344 else:
345 else:
345 p = perm.Permission.permission_name
346 p = perm.Permission.permission_name
346 user.permissions['repositories'][perm.RepoToPerm.
347 user.permissions['repositories'][perm.RepoToPerm.
347 repository.repo_name] = p
348 repository.repo_name] = p
348
349
349 #==================================================================
350 #==================================================================
350 # check if user is part of groups for this repository and fill in
351 # check if user is part of groups for this repository and fill in
351 # (or replace with higher) permissions
352 # (or replace with higher) permissions
352 #==================================================================
353 #==================================================================
353
354
354 #users group global
355 #users group global
355 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
356 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
356 .options(joinedload(UsersGroupToPerm.permission))\
357 .options(joinedload(UsersGroupToPerm.permission))\
357 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
358 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
358 UsersGroupMember.users_group_id))\
359 UsersGroupMember.users_group_id))\
359 .filter(UsersGroupMember.user_id == uid).all()
360 .filter(UsersGroupMember.user_id == uid).all()
360
361
361 for perm in user_perms_from_users_groups:
362 for perm in user_perms_from_users_groups:
362 user.permissions['global'].add(perm.permission.permission_name)
363 user.permissions['global'].add(perm.permission.permission_name)
363
364
364 #users group repositories
365 #users group repositories
365 user_repo_perms_from_users_groups = self.sa.query(
366 user_repo_perms_from_users_groups = self.sa.query(
366 UsersGroupRepoToPerm,
367 UsersGroupRepoToPerm,
367 Permission, Repository,)\
368 Permission, Repository,)\
368 .join((Repository, UsersGroupRepoToPerm.repository_id ==
369 .join((Repository, UsersGroupRepoToPerm.repository_id ==
369 Repository.repo_id))\
370 Repository.repo_id))\
370 .join((Permission, UsersGroupRepoToPerm.permission_id ==
371 .join((Permission, UsersGroupRepoToPerm.permission_id ==
371 Permission.permission_id))\
372 Permission.permission_id))\
372 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
373 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
373 UsersGroupMember.users_group_id))\
374 UsersGroupMember.users_group_id))\
374 .filter(UsersGroupMember.user_id == uid).all()
375 .filter(UsersGroupMember.user_id == uid).all()
375
376
376 for perm in user_repo_perms_from_users_groups:
377 for perm in user_repo_perms_from_users_groups:
377 p = perm.Permission.permission_name
378 p = perm.Permission.permission_name
378 cur_perm = user.permissions['repositories'][perm.
379 cur_perm = user.permissions['repositories'][perm.
379 UsersGroupRepoToPerm.
380 UsersGroupRepoToPerm.
380 repository.repo_name]
381 repository.repo_name]
381 #overwrite permission only if it's greater than permission
382 #overwrite permission only if it's greater than permission
382 # given from other sources
383 # given from other sources
383 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
384 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
384 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
385 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
385 repository.repo_name] = p
386 repository.repo_name] = p
386
387
387 return user
388 return user
@@ -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,125 +1,125 b''
1 import sys
1 import sys
2 from rhodecode import get_version
2 from rhodecode import get_version
3 from rhodecode import __platform__
3 from rhodecode import __platform__
4 from rhodecode import __license__
4 from rhodecode import __license__
5 from rhodecode import PLATFORM_OTHERS
5 from rhodecode import PLATFORM_OTHERS
6
6
7 py_version = sys.version_info
7 py_version = sys.version_info
8
8
9 if py_version < (2, 5):
9 if py_version < (2, 5):
10 raise Exception('RhodeCode requires python 2.5 or later')
10 raise Exception('RhodeCode requires python 2.5 or later')
11
11
12 requirements = [
12 requirements = [
13 "Pylons==1.0.0",
13 "Pylons==1.0.0",
14 "WebHelpers>=1.2",
14 "WebHelpers>=1.2",
15 "formencode==1.2.4",
15 "formencode==1.2.4",
16 "SQLAlchemy>=0.7.2,<0.8",
16 "SQLAlchemy>=0.7.2,<0.8",
17 "Mako>=0.4.2",
17 "Mako>=0.4.2",
18 "pygments>=1.4",
18 "pygments>=1.4",
19 "mercurial>=1.9,<2.0",
19 "mercurial>=1.9,<2.0",
20 "whoosh<1.8",
20 "whoosh<1.8",
21 "celery>=2.2.5,<2.3",
21 "celery>=2.2.5,<2.3",
22 "babel",
22 "babel",
23 "python-dateutil>=1.5.0,<2.0.0",
23 "python-dateutil>=1.5.0,<2.0.0",
24 "dulwich>=0.8.0",
24 "dulwich>=0.8.0",
25 "vcs>=0.2.1.dev",
25 "vcs>=0.2.1.dev",
26 "webob==1.0.8"
26 "webob==1.0.8"
27 ]
27 ]
28
28
29 dependency_links = [
29 dependency_links = [
30 "https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2.1.dev",
30 "https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2.2.dev",
31 "https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2.1.dev",
31 "https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2.2.dev",
32 ]
32 ]
33
33
34 classifiers = ['Development Status :: 4 - Beta',
34 classifiers = ['Development Status :: 4 - Beta',
35 'Environment :: Web Environment',
35 'Environment :: Web Environment',
36 'Framework :: Pylons',
36 'Framework :: Pylons',
37 'Intended Audience :: Developers',
37 'Intended Audience :: Developers',
38 'Operating System :: OS Independent',
38 'Operating System :: OS Independent',
39 'Programming Language :: Python',
39 'Programming Language :: Python',
40 'Programming Language :: Python :: 2.5',
40 'Programming Language :: Python :: 2.5',
41 'Programming Language :: Python :: 2.6',
41 'Programming Language :: Python :: 2.6',
42 'Programming Language :: Python :: 2.7', ]
42 'Programming Language :: Python :: 2.7', ]
43
43
44 if py_version < (2, 6):
44 if py_version < (2, 6):
45 requirements.append("simplejson")
45 requirements.append("simplejson")
46 requirements.append("pysqlite")
46 requirements.append("pysqlite")
47
47
48 if __platform__ in PLATFORM_OTHERS:
48 if __platform__ in PLATFORM_OTHERS:
49 requirements.append("py-bcrypt")
49 requirements.append("py-bcrypt")
50
50
51
51
52 #additional files from project that goes somewhere in the filesystem
52 #additional files from project that goes somewhere in the filesystem
53 #relative to sys.prefix
53 #relative to sys.prefix
54 data_files = []
54 data_files = []
55
55
56 #additional files that goes into package itself
56 #additional files that goes into package itself
57 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
57 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
58
58
59 description = ('Mercurial repository browser/management with '
59 description = ('Mercurial repository browser/management with '
60 'build in push/pull server and full text search')
60 'build in push/pull server and full text search')
61 keywords = ' '.join(['rhodecode', 'rhodiumcode', 'mercurial', 'git',
61 keywords = ' '.join(['rhodecode', 'rhodiumcode', 'mercurial', 'git',
62 'repository management', 'hgweb replacement'
62 'repository management', 'hgweb replacement'
63 'hgwebdir', 'gitweb replacement', 'serving hgweb', ])
63 'hgwebdir', 'gitweb replacement', 'serving hgweb', ])
64 #long description
64 #long description
65 try:
65 try:
66 readme_file = 'README.rst'
66 readme_file = 'README.rst'
67 changelog_file = 'docs/changelog.rst'
67 changelog_file = 'docs/changelog.rst'
68 long_description = open(readme_file).read() + '\n\n' + \
68 long_description = open(readme_file).read() + '\n\n' + \
69 open(changelog_file).read()
69 open(changelog_file).read()
70
70
71 except IOError, err:
71 except IOError, err:
72 sys.stderr.write("[WARNING] Cannot find file specified as "
72 sys.stderr.write("[WARNING] Cannot find file specified as "
73 "long_description (%s)\n or changelog (%s) skipping that file" \
73 "long_description (%s)\n or changelog (%s) skipping that file" \
74 % (readme_file, changelog_file))
74 % (readme_file, changelog_file))
75 long_description = description
75 long_description = description
76
76
77
77
78 try:
78 try:
79 from setuptools import setup, find_packages
79 from setuptools import setup, find_packages
80 except ImportError:
80 except ImportError:
81 from ez_setup import use_setuptools
81 from ez_setup import use_setuptools
82 use_setuptools()
82 use_setuptools()
83 from setuptools import setup, find_packages
83 from setuptools import setup, find_packages
84 #packages
84 #packages
85 packages = find_packages(exclude=['ez_setup'])
85 packages = find_packages(exclude=['ez_setup'])
86
86
87 setup(
87 setup(
88 name='RhodeCode',
88 name='RhodeCode',
89 version=get_version(),
89 version=get_version(),
90 description=description,
90 description=description,
91 long_description=long_description,
91 long_description=long_description,
92 keywords=keywords,
92 keywords=keywords,
93 license=__license__,
93 license=__license__,
94 author='Marcin Kuzminski',
94 author='Marcin Kuzminski',
95 author_email='marcin@python-works.com',
95 author_email='marcin@python-works.com',
96 dependency_links=dependency_links,
96 dependency_links=dependency_links,
97 url='http://rhodecode.org',
97 url='http://rhodecode.org',
98 install_requires=requirements,
98 install_requires=requirements,
99 classifiers=classifiers,
99 classifiers=classifiers,
100 setup_requires=["PasteScript>=1.6.3"],
100 setup_requires=["PasteScript>=1.6.3"],
101 data_files=data_files,
101 data_files=data_files,
102 packages=packages,
102 packages=packages,
103 include_package_data=True,
103 include_package_data=True,
104 test_suite='nose.collector',
104 test_suite='nose.collector',
105 package_data=package_data,
105 package_data=package_data,
106 message_extractors={'rhodecode': [
106 message_extractors={'rhodecode': [
107 ('**.py', 'python', None),
107 ('**.py', 'python', None),
108 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
108 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
109 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
109 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
110 ('public/**', 'ignore', None)]},
110 ('public/**', 'ignore', None)]},
111 zip_safe=False,
111 zip_safe=False,
112 paster_plugins=['PasteScript', 'Pylons'],
112 paster_plugins=['PasteScript', 'Pylons'],
113 entry_points="""
113 entry_points="""
114 [paste.app_factory]
114 [paste.app_factory]
115 main = rhodecode.config.middleware:make_app
115 main = rhodecode.config.middleware:make_app
116
116
117 [paste.app_install]
117 [paste.app_install]
118 main = pylons.util:PylonsInstaller
118 main = pylons.util:PylonsInstaller
119
119
120 [paste.global_paster_command]
120 [paste.global_paster_command]
121 make-index = rhodecode.lib.indexers:MakeIndex
121 make-index = rhodecode.lib.indexers:MakeIndex
122 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
122 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
123 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
123 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
124 """,
124 """,
125 )
125 )
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