##// END OF EJS Templates
merge beta into stable
marcink -
r2074:6c6718c0 merge default
parent child Browse files
Show More
@@ -1,53 +1,54 b''
1 .. _upgrade:
1 .. _upgrade:
2
2
3 Upgrade
3 Upgrade
4 =======
4 =======
5
5
6 Upgrading from Cheese Shop
6 Upgrading from Cheese Shop
7 --------------------------
7 --------------------------
8
8
9 .. note::
9 .. note::
10 Firstly, it is recommended that you **always** perform a database backup
10 Firstly, it is recommended that you **always** perform a database backup
11 before doing an upgrade.
11 before doing an upgrade.
12
12
13 The easiest way to upgrade ``rhodecode`` is to run::
13 The easiest way to upgrade ``rhodecode`` is to run::
14
14
15 easy_install -U rhodecode
15 easy_install -U rhodecode
16
16
17 Or::
17 Or::
18
18
19 pip install --upgrade rhodecode
19 pip install --upgrade rhodecode
20
20
21
21
22 Then make sure you run the following command from the installation directory::
22 Then make sure you run the following command from the installation directory::
23
23
24 paster make-config RhodeCode production.ini
24 paster make-config RhodeCode production.ini
25
25
26 This will display any changes made by the new version of RhodeCode to your
26 This will display any changes made by the new version of RhodeCode to your
27 current configuration. It will try to perform an automerge. It's always better
27 current configuration. It will try to perform an automerge. It's always better
28 to make a backup of your configuration file before hand and recheck the
28 to make a backup of your configuration file before hand and recheck the
29 content after the automerge.
29 content after the automerge.
30
30
31 .. note::
31 .. note::
32 The next steps only apply to upgrading from non bugfix releases eg. from
32 Please always make sure your .ini files are upto date. Often errors are
33 any minor or major releases. Bugfix releases (eg. 1.1.2->1.1.3) will
33 caused by missing params added in new versions.
34 not have any database schema changes or whoosh library updates.
34
35
35
36 It is also recommended that you rebuild the whoosh index after upgrading since
36 It is also recommended that you rebuild the whoosh index after upgrading since
37 the new whoosh version could introduce some incompatible index changes.
37 the new whoosh version could introduce some incompatible index changes. Please
38 Read the changelog to see if there were any changes to whoosh.
38
39
39
40
40 The final step is to upgrade the database. To do this simply run::
41 The final step is to upgrade the database. To do this simply run::
41
42
42 paster upgrade-db production.ini
43 paster upgrade-db production.ini
43
44
44 This will upgrade the schema and update some of the defaults in the database,
45 This will upgrade the schema and update some of the defaults in the database,
45 and will always recheck the settings of the application, if there are no new
46 and will always recheck the settings of the application, if there are no new
46 options that need to be set.
47 options that need to be set.
47
48
48
49
49 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
50 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
50 .. _python: http://www.python.org/
51 .. _python: http://www.python.org/
51 .. _mercurial: http://mercurial.selenic.com/
52 .. _mercurial: http://mercurial.selenic.com/
52 .. _celery: http://celeryproject.org/
53 .. _celery: http://celeryproject.org/
53 .. _rabbitmq: http://www.rabbitmq.com/ No newline at end of file
54 .. _rabbitmq: http://www.rabbitmq.com/
@@ -1,367 +1,367 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28 from collections import defaultdict
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden
29 from webob.exc import HTTPForbidden
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
34 from pylons.decorators import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
39
39
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import EmptyChangeset
43 from rhodecode.lib.utils import EmptyChangeset
44 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment
46 from rhodecode.model.db import ChangesetComment
47 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.lib.diffs import wrapped_diff
49 from rhodecode.lib.diffs import wrapped_diff
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def anchor_url(revision, path):
54 def anchor_url(revision, path):
55 fid = h.FID(revision, path)
55 fid = h.FID(revision, path)
56 return h.url.current(anchor=fid, **request.GET)
56 return h.url.current(anchor=fid, **dict(request.GET))
57
57
58
58
59 def get_ignore_ws(fid, GET):
59 def get_ignore_ws(fid, GET):
60 ig_ws_global = request.GET.get('ignorews')
60 ig_ws_global = request.GET.get('ignorews')
61 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
61 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
62 if ig_ws:
62 if ig_ws:
63 try:
63 try:
64 return int(ig_ws[0].split(':')[-1])
64 return int(ig_ws[0].split(':')[-1])
65 except:
65 except:
66 pass
66 pass
67 return ig_ws_global
67 return ig_ws_global
68
68
69
69
70 def _ignorews_url(fileid=None):
70 def _ignorews_url(fileid=None):
71
71
72 params = defaultdict(list)
72 params = defaultdict(list)
73 lbl = _('show white space')
73 lbl = _('show white space')
74 ig_ws = get_ignore_ws(fileid, request.GET)
74 ig_ws = get_ignore_ws(fileid, request.GET)
75 ln_ctx = get_line_ctx(fileid, request.GET)
75 ln_ctx = get_line_ctx(fileid, request.GET)
76 # global option
76 # global option
77 if fileid is None:
77 if fileid is None:
78 if ig_ws is None:
78 if ig_ws is None:
79 params['ignorews'] += [1]
79 params['ignorews'] += [1]
80 lbl = _('ignore white space')
80 lbl = _('ignore white space')
81 ctx_key = 'context'
81 ctx_key = 'context'
82 ctx_val = ln_ctx
82 ctx_val = ln_ctx
83 # per file options
83 # per file options
84 else:
84 else:
85 if ig_ws is None:
85 if ig_ws is None:
86 params[fileid] += ['WS:1']
86 params[fileid] += ['WS:1']
87 lbl = _('ignore white space')
87 lbl = _('ignore white space')
88
88
89 ctx_key = fileid
89 ctx_key = fileid
90 ctx_val = 'C:%s' % ln_ctx
90 ctx_val = 'C:%s' % ln_ctx
91 # if we have passed in ln_ctx pass it along to our params
91 # if we have passed in ln_ctx pass it along to our params
92 if ln_ctx:
92 if ln_ctx:
93 params[ctx_key] += [ctx_val]
93 params[ctx_key] += [ctx_val]
94
94
95 params['anchor'] = fileid
95 params['anchor'] = fileid
96 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
96 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
97 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
97 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
98
98
99
99
100 def get_line_ctx(fid, GET):
100 def get_line_ctx(fid, GET):
101 ln_ctx_global = request.GET.get('context')
101 ln_ctx_global = request.GET.get('context')
102 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
102 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
103
103
104 if ln_ctx:
104 if ln_ctx:
105 retval = ln_ctx[0].split(':')[-1]
105 retval = ln_ctx[0].split(':')[-1]
106 else:
106 else:
107 retval = ln_ctx_global
107 retval = ln_ctx_global
108
108
109 try:
109 try:
110 return int(retval)
110 return int(retval)
111 except:
111 except:
112 return
112 return
113
113
114
114
115 def _context_url(fileid=None):
115 def _context_url(fileid=None):
116 """
116 """
117 Generates url for context lines
117 Generates url for context lines
118
118
119 :param fileid:
119 :param fileid:
120 """
120 """
121 ig_ws = get_ignore_ws(fileid, request.GET)
121 ig_ws = get_ignore_ws(fileid, request.GET)
122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
122 ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
123
123
124 params = defaultdict(list)
124 params = defaultdict(list)
125
125
126 # global option
126 # global option
127 if fileid is None:
127 if fileid is None:
128 if ln_ctx > 0:
128 if ln_ctx > 0:
129 params['context'] += [ln_ctx]
129 params['context'] += [ln_ctx]
130
130
131 if ig_ws:
131 if ig_ws:
132 ig_ws_key = 'ignorews'
132 ig_ws_key = 'ignorews'
133 ig_ws_val = 1
133 ig_ws_val = 1
134
134
135 # per file option
135 # per file option
136 else:
136 else:
137 params[fileid] += ['C:%s' % ln_ctx]
137 params[fileid] += ['C:%s' % ln_ctx]
138 ig_ws_key = fileid
138 ig_ws_key = fileid
139 ig_ws_val = 'WS:%s' % 1
139 ig_ws_val = 'WS:%s' % 1
140
140
141 if ig_ws:
141 if ig_ws:
142 params[ig_ws_key] += [ig_ws_val]
142 params[ig_ws_key] += [ig_ws_val]
143
143
144 lbl = _('%s line context') % ln_ctx
144 lbl = _('%s line context') % ln_ctx
145
145
146 params['anchor'] = fileid
146 params['anchor'] = fileid
147 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
147 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
148 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
148 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
149
149
150
150
151 class ChangesetController(BaseRepoController):
151 class ChangesetController(BaseRepoController):
152
152
153 @LoginRequired()
153 @LoginRequired()
154 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
154 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
155 'repository.admin')
155 'repository.admin')
156 def __before__(self):
156 def __before__(self):
157 super(ChangesetController, self).__before__()
157 super(ChangesetController, self).__before__()
158 c.affected_files_cut_off = 60
158 c.affected_files_cut_off = 60
159
159
160 def index(self, revision):
160 def index(self, revision):
161
161
162 c.anchor_url = anchor_url
162 c.anchor_url = anchor_url
163 c.ignorews_url = _ignorews_url
163 c.ignorews_url = _ignorews_url
164 c.context_url = _context_url
164 c.context_url = _context_url
165
165
166 #get ranges of revisions if preset
166 #get ranges of revisions if preset
167 rev_range = revision.split('...')[:2]
167 rev_range = revision.split('...')[:2]
168 enable_comments = True
168 enable_comments = True
169 try:
169 try:
170 if len(rev_range) == 2:
170 if len(rev_range) == 2:
171 enable_comments = False
171 enable_comments = False
172 rev_start = rev_range[0]
172 rev_start = rev_range[0]
173 rev_end = rev_range[1]
173 rev_end = rev_range[1]
174 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
174 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
175 end=rev_end)
175 end=rev_end)
176 else:
176 else:
177 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
177 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
178
178
179 c.cs_ranges = list(rev_ranges)
179 c.cs_ranges = list(rev_ranges)
180 if not c.cs_ranges:
180 if not c.cs_ranges:
181 raise RepositoryError('Changeset range returned empty result')
181 raise RepositoryError('Changeset range returned empty result')
182
182
183 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
183 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
184 log.error(traceback.format_exc())
184 log.error(traceback.format_exc())
185 h.flash(str(e), category='warning')
185 h.flash(str(e), category='warning')
186 return redirect(url('home'))
186 return redirect(url('home'))
187
187
188 c.changes = OrderedDict()
188 c.changes = OrderedDict()
189
189
190 c.lines_added = 0 # count of lines added
190 c.lines_added = 0 # count of lines added
191 c.lines_deleted = 0 # count of lines removes
191 c.lines_deleted = 0 # count of lines removes
192
192
193 cumulative_diff = 0
193 cumulative_diff = 0
194 c.cut_off = False # defines if cut off limit is reached
194 c.cut_off = False # defines if cut off limit is reached
195
195
196 c.comments = []
196 c.comments = []
197 c.inline_comments = []
197 c.inline_comments = []
198 c.inline_cnt = 0
198 c.inline_cnt = 0
199 # Iterate over ranges (default changeset view is always one changeset)
199 # Iterate over ranges (default changeset view is always one changeset)
200 for changeset in c.cs_ranges:
200 for changeset in c.cs_ranges:
201 c.comments.extend(ChangesetCommentsModel()\
201 c.comments.extend(ChangesetCommentsModel()\
202 .get_comments(c.rhodecode_db_repo.repo_id,
202 .get_comments(c.rhodecode_db_repo.repo_id,
203 changeset.raw_id))
203 changeset.raw_id))
204 inlines = ChangesetCommentsModel()\
204 inlines = ChangesetCommentsModel()\
205 .get_inline_comments(c.rhodecode_db_repo.repo_id,
205 .get_inline_comments(c.rhodecode_db_repo.repo_id,
206 changeset.raw_id)
206 changeset.raw_id)
207 c.inline_comments.extend(inlines)
207 c.inline_comments.extend(inlines)
208 c.changes[changeset.raw_id] = []
208 c.changes[changeset.raw_id] = []
209 try:
209 try:
210 changeset_parent = changeset.parents[0]
210 changeset_parent = changeset.parents[0]
211 except IndexError:
211 except IndexError:
212 changeset_parent = None
212 changeset_parent = None
213
213
214 #==================================================================
214 #==================================================================
215 # ADDED FILES
215 # ADDED FILES
216 #==================================================================
216 #==================================================================
217 for node in changeset.added:
217 for node in changeset.added:
218 fid = h.FID(revision, node.path)
218 fid = h.FID(revision, node.path)
219 line_context_lcl = get_line_ctx(fid, request.GET)
219 line_context_lcl = get_line_ctx(fid, request.GET)
220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
220 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
221 lim = self.cut_off_limit
221 lim = self.cut_off_limit
222 if cumulative_diff > self.cut_off_limit:
222 if cumulative_diff > self.cut_off_limit:
223 lim = -1
223 lim = -1
224 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
224 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=None,
225 filenode_new=node,
225 filenode_new=node,
226 cut_off_limit=lim,
226 cut_off_limit=lim,
227 ignore_whitespace=ign_whitespace_lcl,
227 ignore_whitespace=ign_whitespace_lcl,
228 line_context=line_context_lcl,
228 line_context=line_context_lcl,
229 enable_comments=enable_comments)
229 enable_comments=enable_comments)
230 cumulative_diff += size
230 cumulative_diff += size
231 c.lines_added += st[0]
231 c.lines_added += st[0]
232 c.lines_deleted += st[1]
232 c.lines_deleted += st[1]
233 c.changes[changeset.raw_id].append(('added', node, diff,
233 c.changes[changeset.raw_id].append(('added', node, diff,
234 cs1, cs2, st))
234 cs1, cs2, st))
235
235
236 #==================================================================
236 #==================================================================
237 # CHANGED FILES
237 # CHANGED FILES
238 #==================================================================
238 #==================================================================
239 for node in changeset.changed:
239 for node in changeset.changed:
240 try:
240 try:
241 filenode_old = changeset_parent.get_node(node.path)
241 filenode_old = changeset_parent.get_node(node.path)
242 except ChangesetError:
242 except ChangesetError:
243 log.warning('Unable to fetch parent node for diff')
243 log.warning('Unable to fetch parent node for diff')
244 filenode_old = FileNode(node.path, '', EmptyChangeset())
244 filenode_old = FileNode(node.path, '', EmptyChangeset())
245
245
246 fid = h.FID(revision, node.path)
246 fid = h.FID(revision, node.path)
247 line_context_lcl = get_line_ctx(fid, request.GET)
247 line_context_lcl = get_line_ctx(fid, request.GET)
248 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
248 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
249 lim = self.cut_off_limit
249 lim = self.cut_off_limit
250 if cumulative_diff > self.cut_off_limit:
250 if cumulative_diff > self.cut_off_limit:
251 lim = -1
251 lim = -1
252 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
252 size, cs1, cs2, diff, st = wrapped_diff(filenode_old=filenode_old,
253 filenode_new=node,
253 filenode_new=node,
254 cut_off_limit=lim,
254 cut_off_limit=lim,
255 ignore_whitespace=ign_whitespace_lcl,
255 ignore_whitespace=ign_whitespace_lcl,
256 line_context=line_context_lcl,
256 line_context=line_context_lcl,
257 enable_comments=enable_comments)
257 enable_comments=enable_comments)
258 cumulative_diff += size
258 cumulative_diff += size
259 c.lines_added += st[0]
259 c.lines_added += st[0]
260 c.lines_deleted += st[1]
260 c.lines_deleted += st[1]
261 c.changes[changeset.raw_id].append(('changed', node, diff,
261 c.changes[changeset.raw_id].append(('changed', node, diff,
262 cs1, cs2, st))
262 cs1, cs2, st))
263
263
264 #==================================================================
264 #==================================================================
265 # REMOVED FILES
265 # REMOVED FILES
266 #==================================================================
266 #==================================================================
267 for node in changeset.removed:
267 for node in changeset.removed:
268 c.changes[changeset.raw_id].append(('removed', node, None,
268 c.changes[changeset.raw_id].append(('removed', node, None,
269 None, None, (0, 0)))
269 None, None, (0, 0)))
270
270
271 # count inline comments
271 # count inline comments
272 for path, lines in c.inline_comments:
272 for path, lines in c.inline_comments:
273 for comments in lines.values():
273 for comments in lines.values():
274 c.inline_cnt += len(comments)
274 c.inline_cnt += len(comments)
275
275
276 if len(c.cs_ranges) == 1:
276 if len(c.cs_ranges) == 1:
277 c.changeset = c.cs_ranges[0]
277 c.changeset = c.cs_ranges[0]
278 c.changes = c.changes[c.changeset.raw_id]
278 c.changes = c.changes[c.changeset.raw_id]
279
279
280 return render('changeset/changeset.html')
280 return render('changeset/changeset.html')
281 else:
281 else:
282 return render('changeset/changeset_range.html')
282 return render('changeset/changeset_range.html')
283
283
284 def raw_changeset(self, revision):
284 def raw_changeset(self, revision):
285
285
286 method = request.GET.get('diff', 'show')
286 method = request.GET.get('diff', 'show')
287 ignore_whitespace = request.GET.get('ignorews') == '1'
287 ignore_whitespace = request.GET.get('ignorews') == '1'
288 line_context = request.GET.get('context', 3)
288 line_context = request.GET.get('context', 3)
289 try:
289 try:
290 c.scm_type = c.rhodecode_repo.alias
290 c.scm_type = c.rhodecode_repo.alias
291 c.changeset = c.rhodecode_repo.get_changeset(revision)
291 c.changeset = c.rhodecode_repo.get_changeset(revision)
292 except RepositoryError:
292 except RepositoryError:
293 log.error(traceback.format_exc())
293 log.error(traceback.format_exc())
294 return redirect(url('home'))
294 return redirect(url('home'))
295 else:
295 else:
296 try:
296 try:
297 c.changeset_parent = c.changeset.parents[0]
297 c.changeset_parent = c.changeset.parents[0]
298 except IndexError:
298 except IndexError:
299 c.changeset_parent = None
299 c.changeset_parent = None
300 c.changes = []
300 c.changes = []
301
301
302 for node in c.changeset.added:
302 for node in c.changeset.added:
303 filenode_old = FileNode(node.path, '')
303 filenode_old = FileNode(node.path, '')
304 if filenode_old.is_binary or node.is_binary:
304 if filenode_old.is_binary or node.is_binary:
305 diff = _('binary file') + '\n'
305 diff = _('binary file') + '\n'
306 else:
306 else:
307 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
307 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
308 ignore_whitespace=ignore_whitespace,
308 ignore_whitespace=ignore_whitespace,
309 context=line_context)
309 context=line_context)
310 diff = diffs.DiffProcessor(f_gitdiff,
310 diff = diffs.DiffProcessor(f_gitdiff,
311 format='gitdiff').raw_diff()
311 format='gitdiff').raw_diff()
312
312
313 cs1 = None
313 cs1 = None
314 cs2 = node.last_changeset.raw_id
314 cs2 = node.last_changeset.raw_id
315 c.changes.append(('added', node, diff, cs1, cs2))
315 c.changes.append(('added', node, diff, cs1, cs2))
316
316
317 for node in c.changeset.changed:
317 for node in c.changeset.changed:
318 filenode_old = c.changeset_parent.get_node(node.path)
318 filenode_old = c.changeset_parent.get_node(node.path)
319 if filenode_old.is_binary or node.is_binary:
319 if filenode_old.is_binary or node.is_binary:
320 diff = _('binary file')
320 diff = _('binary file')
321 else:
321 else:
322 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
322 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
323 ignore_whitespace=ignore_whitespace,
323 ignore_whitespace=ignore_whitespace,
324 context=line_context)
324 context=line_context)
325 diff = diffs.DiffProcessor(f_gitdiff,
325 diff = diffs.DiffProcessor(f_gitdiff,
326 format='gitdiff').raw_diff()
326 format='gitdiff').raw_diff()
327
327
328 cs1 = filenode_old.last_changeset.raw_id
328 cs1 = filenode_old.last_changeset.raw_id
329 cs2 = node.last_changeset.raw_id
329 cs2 = node.last_changeset.raw_id
330 c.changes.append(('changed', node, diff, cs1, cs2))
330 c.changes.append(('changed', node, diff, cs1, cs2))
331
331
332 response.content_type = 'text/plain'
332 response.content_type = 'text/plain'
333
333
334 if method == 'download':
334 if method == 'download':
335 response.content_disposition = 'attachment; filename=%s.patch' \
335 response.content_disposition = 'attachment; filename=%s.patch' \
336 % revision
336 % revision
337
337
338 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
338 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
339 c.changeset.parents])
339 c.changeset.parents])
340
340
341 c.diffs = ''
341 c.diffs = ''
342 for x in c.changes:
342 for x in c.changes:
343 c.diffs += x[2]
343 c.diffs += x[2]
344
344
345 return render('changeset/raw_changeset.html')
345 return render('changeset/raw_changeset.html')
346
346
347 def comment(self, repo_name, revision):
347 def comment(self, repo_name, revision):
348 ChangesetCommentsModel().create(text=request.POST.get('text'),
348 ChangesetCommentsModel().create(text=request.POST.get('text'),
349 repo_id=c.rhodecode_db_repo.repo_id,
349 repo_id=c.rhodecode_db_repo.repo_id,
350 user_id=c.rhodecode_user.user_id,
350 user_id=c.rhodecode_user.user_id,
351 revision=revision,
351 revision=revision,
352 f_path=request.POST.get('f_path'),
352 f_path=request.POST.get('f_path'),
353 line_no=request.POST.get('line'))
353 line_no=request.POST.get('line'))
354 Session.commit()
354 Session.commit()
355 return redirect(h.url('changeset_home', repo_name=repo_name,
355 return redirect(h.url('changeset_home', repo_name=repo_name,
356 revision=revision))
356 revision=revision))
357
357
358 @jsonify
358 @jsonify
359 def delete_comment(self, repo_name, comment_id):
359 def delete_comment(self, repo_name, comment_id):
360 co = ChangesetComment.get(comment_id)
360 co = ChangesetComment.get(comment_id)
361 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
361 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
362 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
362 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
363 ChangesetCommentsModel().delete(comment=co)
363 ChangesetCommentsModel().delete(comment=co)
364 Session.commit()
364 Session.commit()
365 return True
365 return True
366 else:
366 else:
367 raise HTTPForbidden()
367 raise HTTPForbidden()
@@ -1,233 +1,233 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or 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 traceback
26 import traceback
27 import calendar
27 import calendar
28 import logging
28 import logging
29 from time import mktime
29 from time import mktime
30 from datetime import timedelta, date
30 from datetime import timedelta, date
31 from itertools import product
32 from urlparse import urlparse
31 from urlparse import urlparse
32 from rhodecode.lib.compat import product
33
33
34 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
34 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
35 NodeDoesNotExistError
35 NodeDoesNotExistError
36
36
37 from pylons import tmpl_context as c, request, url, config
37 from pylons import tmpl_context as c, request, url, config
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from beaker.cache import cache_region, region_invalidate
40 from beaker.cache import cache_region, region_invalidate
41
41
42 from rhodecode.model.db import Statistics, CacheInvalidation
42 from rhodecode.model.db import Statistics, CacheInvalidation
43 from rhodecode.lib import ALL_READMES, ALL_EXTS
43 from rhodecode.lib import ALL_READMES, ALL_EXTS
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 from rhodecode.lib.markup_renderer import MarkupRenderer
47 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.celerylib import run_task
48 from rhodecode.lib.celerylib import run_task
49 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
49 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
50 LANGUAGES_EXTENSIONS_MAP
50 LANGUAGES_EXTENSIONS_MAP
51 from rhodecode.lib.helpers import RepoPage
51 from rhodecode.lib.helpers import RepoPage
52 from rhodecode.lib.compat import json, OrderedDict
52 from rhodecode.lib.compat import json, OrderedDict
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
56 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
57 sorted(list(product(ALL_READMES, ALL_EXTS)),
57 sorted(list(product(ALL_READMES, ALL_EXTS)),
58 key=lambda y:y[0][1] + y[1][1])]
58 key=lambda y:y[0][1] + y[1][1])]
59
59
60
60
61 class SummaryController(BaseRepoController):
61 class SummaryController(BaseRepoController):
62
62
63 @LoginRequired()
63 @LoginRequired()
64 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
64 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
65 'repository.admin')
65 'repository.admin')
66 def __before__(self):
66 def __before__(self):
67 super(SummaryController, self).__before__()
67 super(SummaryController, self).__before__()
68
68
69 def index(self, repo_name):
69 def index(self, repo_name):
70 c.dbrepo = dbrepo = c.rhodecode_db_repo
70 c.dbrepo = dbrepo = c.rhodecode_db_repo
71 c.following = self.scm_model.is_following_repo(repo_name,
71 c.following = self.scm_model.is_following_repo(repo_name,
72 self.rhodecode_user.user_id)
72 self.rhodecode_user.user_id)
73
73
74 def url_generator(**kw):
74 def url_generator(**kw):
75 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
75 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
76
76
77 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
77 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
78 items_per_page=10, url=url_generator)
78 items_per_page=10, url=url_generator)
79
79
80 if self.rhodecode_user.username == 'default':
80 if self.rhodecode_user.username == 'default':
81 # for default(anonymous) user we don't need to pass credentials
81 # for default(anonymous) user we don't need to pass credentials
82 username = ''
82 username = ''
83 password = ''
83 password = ''
84 else:
84 else:
85 username = str(self.rhodecode_user.username)
85 username = str(self.rhodecode_user.username)
86 password = '@'
86 password = '@'
87
87
88 parsed_url = urlparse(url.current(qualified=True))
88 parsed_url = urlparse(url.current(qualified=True))
89
89
90 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
90 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
91
91
92 uri_tmpl = config.get('clone_uri', default_clone_uri)
92 uri_tmpl = config.get('clone_uri', default_clone_uri)
93 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
93 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
94
94
95 uri_dict = {
95 uri_dict = {
96 'user': username,
96 'user': username,
97 'pass': password,
97 'pass': password,
98 'scheme': parsed_url.scheme,
98 'scheme': parsed_url.scheme,
99 'netloc': parsed_url.netloc,
99 'netloc': parsed_url.netloc,
100 'path': parsed_url.path
100 'path': parsed_url.path
101 }
101 }
102 uri = uri_tmpl % uri_dict
102 uri = uri_tmpl % uri_dict
103 # generate another clone url by id
103 # generate another clone url by id
104 uri_dict.update({'path': '/_%s' % c.dbrepo.repo_id})
104 uri_dict.update({'path': '/_%s' % c.dbrepo.repo_id})
105 uri_id = uri_tmpl % uri_dict
105 uri_id = uri_tmpl % uri_dict
106
106
107 c.clone_repo_url = uri
107 c.clone_repo_url = uri
108 c.clone_repo_url_id = uri_id
108 c.clone_repo_url_id = uri_id
109 c.repo_tags = OrderedDict()
109 c.repo_tags = OrderedDict()
110 for name, hash in c.rhodecode_repo.tags.items()[:10]:
110 for name, hash in c.rhodecode_repo.tags.items()[:10]:
111 try:
111 try:
112 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
112 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
113 except ChangesetError:
113 except ChangesetError:
114 c.repo_tags[name] = EmptyChangeset(hash)
114 c.repo_tags[name] = EmptyChangeset(hash)
115
115
116 c.repo_branches = OrderedDict()
116 c.repo_branches = OrderedDict()
117 for name, hash in c.rhodecode_repo.branches.items()[:10]:
117 for name, hash in c.rhodecode_repo.branches.items()[:10]:
118 try:
118 try:
119 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
119 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
120 except ChangesetError:
120 except ChangesetError:
121 c.repo_branches[name] = EmptyChangeset(hash)
121 c.repo_branches[name] = EmptyChangeset(hash)
122
122
123 td = date.today() + timedelta(days=1)
123 td = date.today() + timedelta(days=1)
124 td_1m = td - timedelta(days=calendar.mdays[td.month])
124 td_1m = td - timedelta(days=calendar.mdays[td.month])
125 td_1y = td - timedelta(days=365)
125 td_1y = td - timedelta(days=365)
126
126
127 ts_min_m = mktime(td_1m.timetuple())
127 ts_min_m = mktime(td_1m.timetuple())
128 ts_min_y = mktime(td_1y.timetuple())
128 ts_min_y = mktime(td_1y.timetuple())
129 ts_max_y = mktime(td.timetuple())
129 ts_max_y = mktime(td.timetuple())
130
130
131 if dbrepo.enable_statistics:
131 if dbrepo.enable_statistics:
132 c.show_stats = True
132 c.show_stats = True
133 c.no_data_msg = _('No data loaded yet')
133 c.no_data_msg = _('No data loaded yet')
134 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
134 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
135 else:
135 else:
136 c.show_stats = False
136 c.show_stats = False
137 c.no_data_msg = _('Statistics are disabled for this repository')
137 c.no_data_msg = _('Statistics are disabled for this repository')
138 c.ts_min = ts_min_m
138 c.ts_min = ts_min_m
139 c.ts_max = ts_max_y
139 c.ts_max = ts_max_y
140
140
141 stats = self.sa.query(Statistics)\
141 stats = self.sa.query(Statistics)\
142 .filter(Statistics.repository == dbrepo)\
142 .filter(Statistics.repository == dbrepo)\
143 .scalar()
143 .scalar()
144
144
145 c.stats_percentage = 0
145 c.stats_percentage = 0
146
146
147 if stats and stats.languages:
147 if stats and stats.languages:
148 c.no_data = False is dbrepo.enable_statistics
148 c.no_data = False is dbrepo.enable_statistics
149 lang_stats_d = json.loads(stats.languages)
149 lang_stats_d = json.loads(stats.languages)
150 c.commit_data = stats.commit_activity
150 c.commit_data = stats.commit_activity
151 c.overview_data = stats.commit_activity_combined
151 c.overview_data = stats.commit_activity_combined
152
152
153 lang_stats = ((x, {"count": y,
153 lang_stats = ((x, {"count": y,
154 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
154 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
155 for x, y in lang_stats_d.items())
155 for x, y in lang_stats_d.items())
156
156
157 c.trending_languages = json.dumps(
157 c.trending_languages = json.dumps(
158 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
158 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
159 )
159 )
160 last_rev = stats.stat_on_revision + 1
160 last_rev = stats.stat_on_revision + 1
161 c.repo_last_rev = c.rhodecode_repo.count()\
161 c.repo_last_rev = c.rhodecode_repo.count()\
162 if c.rhodecode_repo.revisions else 0
162 if c.rhodecode_repo.revisions else 0
163 if last_rev == 0 or c.repo_last_rev == 0:
163 if last_rev == 0 or c.repo_last_rev == 0:
164 pass
164 pass
165 else:
165 else:
166 c.stats_percentage = '%.2f' % ((float((last_rev)) /
166 c.stats_percentage = '%.2f' % ((float((last_rev)) /
167 c.repo_last_rev) * 100)
167 c.repo_last_rev) * 100)
168 else:
168 else:
169 c.commit_data = json.dumps({})
169 c.commit_data = json.dumps({})
170 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
170 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
171 c.trending_languages = json.dumps({})
171 c.trending_languages = json.dumps({})
172 c.no_data = True
172 c.no_data = True
173
173
174 c.enable_downloads = dbrepo.enable_downloads
174 c.enable_downloads = dbrepo.enable_downloads
175 if c.enable_downloads:
175 if c.enable_downloads:
176 c.download_options = self._get_download_links(c.rhodecode_repo)
176 c.download_options = self._get_download_links(c.rhodecode_repo)
177
177
178 c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_repo)
178 c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_repo)
179 return render('summary/summary.html')
179 return render('summary/summary.html')
180
180
181 def __get_readme_data(self, repo):
181 def __get_readme_data(self, repo):
182
182
183 @cache_region('long_term')
183 @cache_region('long_term')
184 def _get_readme_from_cache(key):
184 def _get_readme_from_cache(key):
185 readme_data = None
185 readme_data = None
186 readme_file = None
186 readme_file = None
187 log.debug('Fetching readme file')
187 log.debug('Fetching readme file')
188 try:
188 try:
189 cs = repo.get_changeset('tip')
189 cs = repo.get_changeset('tip')
190 renderer = MarkupRenderer()
190 renderer = MarkupRenderer()
191 for f in README_FILES:
191 for f in README_FILES:
192 try:
192 try:
193 readme = cs.get_node(f)
193 readme = cs.get_node(f)
194 readme_file = f
194 readme_file = f
195 readme_data = renderer.render(readme.content, f)
195 readme_data = renderer.render(readme.content, f)
196 log.debug('Found readme %s' % readme_file)
196 log.debug('Found readme %s' % readme_file)
197 break
197 break
198 except NodeDoesNotExistError:
198 except NodeDoesNotExistError:
199 continue
199 continue
200 except ChangesetError:
200 except ChangesetError:
201 pass
201 pass
202 except EmptyRepositoryError:
202 except EmptyRepositoryError:
203 pass
203 pass
204 except Exception:
204 except Exception:
205 log.error(traceback.format_exc())
205 log.error(traceback.format_exc())
206
206
207 return readme_data, readme_file
207 return readme_data, readme_file
208
208
209 key = repo.name + '_README'
209 key = repo.name + '_README'
210 inv = CacheInvalidation.invalidate(key)
210 inv = CacheInvalidation.invalidate(key)
211 if inv is not None:
211 if inv is not None:
212 region_invalidate(_get_readme_from_cache, None, key)
212 region_invalidate(_get_readme_from_cache, None, key)
213 CacheInvalidation.set_valid(inv.cache_key)
213 CacheInvalidation.set_valid(inv.cache_key)
214 return _get_readme_from_cache(key)
214 return _get_readme_from_cache(key)
215
215
216 def _get_download_links(self, repo):
216 def _get_download_links(self, repo):
217
217
218 download_l = []
218 download_l = []
219
219
220 branches_group = ([], _("Branches"))
220 branches_group = ([], _("Branches"))
221 tags_group = ([], _("Tags"))
221 tags_group = ([], _("Tags"))
222
222
223 for name, chs in c.rhodecode_repo.branches.items():
223 for name, chs in c.rhodecode_repo.branches.items():
224 #chs = chs.split(':')[-1]
224 #chs = chs.split(':')[-1]
225 branches_group[0].append((chs, name),)
225 branches_group[0].append((chs, name),)
226 download_l.append(branches_group)
226 download_l.append(branches_group)
227
227
228 for name, chs in c.rhodecode_repo.tags.items():
228 for name, chs in c.rhodecode_repo.tags.items():
229 #chs = chs.split(':')[-1]
229 #chs = chs.split(':')[-1]
230 tags_group[0].append((chs, name),)
230 tags_group[0].append((chs, name),)
231 download_l.append(tags_group)
231 download_l.append(tags_group)
232
232
233 return download_l
233 return download_l
@@ -1,381 +1,399 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.compat
3 rhodecode.lib.compat
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Python backward compatibility functions and common libs
6 Python backward compatibility functions and common libs
7
7
8
8
9 :created_on: Oct 7, 2011
9 :created_on: Oct 7, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 from rhodecode import __platform__, PLATFORM_WIN
28 from rhodecode import __platform__, PLATFORM_WIN
29
29
30 #==============================================================================
30 #==============================================================================
31 # json
31 # json
32 #==============================================================================
32 #==============================================================================
33 try:
33 try:
34 import json
34 import json
35 except ImportError:
35 except ImportError:
36 import simplejson as json
36 import simplejson as json
37
37
38
38
39 #==============================================================================
39 #==============================================================================
40 # izip_longest
40 # izip_longest
41 #==============================================================================
41 #==============================================================================
42 try:
42 try:
43 from itertools import izip_longest
43 from itertools import izip_longest
44 except ImportError:
44 except ImportError:
45 import itertools
45 import itertools
46
46
47 def izip_longest(*args, **kwds): # noqa
47 def izip_longest(*args, **kwds): # noqa
48 fillvalue = kwds.get("fillvalue")
48 fillvalue = kwds.get("fillvalue")
49
49
50 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
50 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
51 yield counter() # yields the fillvalue, or raises IndexError
51 yield counter() # yields the fillvalue, or raises IndexError
52
52
53 fillers = itertools.repeat(fillvalue)
53 fillers = itertools.repeat(fillvalue)
54 iters = [itertools.chain(it, sentinel(), fillers)
54 iters = [itertools.chain(it, sentinel(), fillers)
55 for it in args]
55 for it in args]
56 try:
56 try:
57 for tup in itertools.izip(*iters):
57 for tup in itertools.izip(*iters):
58 yield tup
58 yield tup
59 except IndexError:
59 except IndexError:
60 pass
60 pass
61
61
62
62
63 #==============================================================================
63 #==============================================================================
64 # OrderedDict
64 # OrderedDict
65 #==============================================================================
65 #==============================================================================
66
66
67 # Python Software Foundation License
67 # Python Software Foundation License
68
68
69 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
69 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
70 # "!=" should be faster.
70 # "!=" should be faster.
71 class _Nil(object):
71 class _Nil(object):
72
72
73 def __repr__(self):
73 def __repr__(self):
74 return "nil"
74 return "nil"
75
75
76 def __eq__(self, other):
76 def __eq__(self, other):
77 if (isinstance(other, _Nil)):
77 if (isinstance(other, _Nil)):
78 return True
78 return True
79 else:
79 else:
80 return NotImplemented
80 return NotImplemented
81
81
82 def __ne__(self, other):
82 def __ne__(self, other):
83 if (isinstance(other, _Nil)):
83 if (isinstance(other, _Nil)):
84 return False
84 return False
85 else:
85 else:
86 return NotImplemented
86 return NotImplemented
87
87
88 _nil = _Nil()
88 _nil = _Nil()
89
89
90
90
91 class _odict(object):
91 class _odict(object):
92 """Ordered dict data structure, with O(1) complexity for dict operations
92 """Ordered dict data structure, with O(1) complexity for dict operations
93 that modify one element.
93 that modify one element.
94
94
95 Overwriting values doesn't change their original sequential order.
95 Overwriting values doesn't change their original sequential order.
96 """
96 """
97
97
98 def _dict_impl(self):
98 def _dict_impl(self):
99 return None
99 return None
100
100
101 def __init__(self, data=(), **kwds):
101 def __init__(self, data=(), **kwds):
102 """This doesn't accept keyword initialization as normal dicts to avoid
102 """This doesn't accept keyword initialization as normal dicts to avoid
103 a trap - inside a function or method the keyword args are accessible
103 a trap - inside a function or method the keyword args are accessible
104 only as a dict, without a defined order, so their original order is
104 only as a dict, without a defined order, so their original order is
105 lost.
105 lost.
106 """
106 """
107 if kwds:
107 if kwds:
108 raise TypeError("__init__() of ordered dict takes no keyword "
108 raise TypeError("__init__() of ordered dict takes no keyword "
109 "arguments to avoid an ordering trap.")
109 "arguments to avoid an ordering trap.")
110 self._dict_impl().__init__(self)
110 self._dict_impl().__init__(self)
111 # If you give a normal dict, then the order of elements is undefined
111 # If you give a normal dict, then the order of elements is undefined
112 if hasattr(data, "iteritems"):
112 if hasattr(data, "iteritems"):
113 for key, val in data.iteritems():
113 for key, val in data.iteritems():
114 self[key] = val
114 self[key] = val
115 else:
115 else:
116 for key, val in data:
116 for key, val in data:
117 self[key] = val
117 self[key] = val
118
118
119 # Double-linked list header
119 # Double-linked list header
120 def _get_lh(self):
120 def _get_lh(self):
121 dict_impl = self._dict_impl()
121 dict_impl = self._dict_impl()
122 if not hasattr(self, '_lh'):
122 if not hasattr(self, '_lh'):
123 dict_impl.__setattr__(self, '_lh', _nil)
123 dict_impl.__setattr__(self, '_lh', _nil)
124 return dict_impl.__getattribute__(self, '_lh')
124 return dict_impl.__getattribute__(self, '_lh')
125
125
126 def _set_lh(self, val):
126 def _set_lh(self, val):
127 self._dict_impl().__setattr__(self, '_lh', val)
127 self._dict_impl().__setattr__(self, '_lh', val)
128
128
129 lh = property(_get_lh, _set_lh)
129 lh = property(_get_lh, _set_lh)
130
130
131 # Double-linked list tail
131 # Double-linked list tail
132 def _get_lt(self):
132 def _get_lt(self):
133 dict_impl = self._dict_impl()
133 dict_impl = self._dict_impl()
134 if not hasattr(self, '_lt'):
134 if not hasattr(self, '_lt'):
135 dict_impl.__setattr__(self, '_lt', _nil)
135 dict_impl.__setattr__(self, '_lt', _nil)
136 return dict_impl.__getattribute__(self, '_lt')
136 return dict_impl.__getattribute__(self, '_lt')
137
137
138 def _set_lt(self, val):
138 def _set_lt(self, val):
139 self._dict_impl().__setattr__(self, '_lt', val)
139 self._dict_impl().__setattr__(self, '_lt', val)
140
140
141 lt = property(_get_lt, _set_lt)
141 lt = property(_get_lt, _set_lt)
142
142
143 def __getitem__(self, key):
143 def __getitem__(self, key):
144 return self._dict_impl().__getitem__(self, key)[1]
144 return self._dict_impl().__getitem__(self, key)[1]
145
145
146 def __setitem__(self, key, val):
146 def __setitem__(self, key, val):
147 dict_impl = self._dict_impl()
147 dict_impl = self._dict_impl()
148 try:
148 try:
149 dict_impl.__getitem__(self, key)[1] = val
149 dict_impl.__getitem__(self, key)[1] = val
150 except KeyError:
150 except KeyError:
151 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
151 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
152 dict_impl.__setitem__(self, key, new)
152 dict_impl.__setitem__(self, key, new)
153 if dict_impl.__getattribute__(self, 'lt') == _nil:
153 if dict_impl.__getattribute__(self, 'lt') == _nil:
154 dict_impl.__setattr__(self, 'lh', key)
154 dict_impl.__setattr__(self, 'lh', key)
155 else:
155 else:
156 dict_impl.__getitem__(
156 dict_impl.__getitem__(
157 self, dict_impl.__getattribute__(self, 'lt'))[2] = key
157 self, dict_impl.__getattribute__(self, 'lt'))[2] = key
158 dict_impl.__setattr__(self, 'lt', key)
158 dict_impl.__setattr__(self, 'lt', key)
159
159
160 def __delitem__(self, key):
160 def __delitem__(self, key):
161 dict_impl = self._dict_impl()
161 dict_impl = self._dict_impl()
162 pred, _, succ = self._dict_impl().__getitem__(self, key)
162 pred, _, succ = self._dict_impl().__getitem__(self, key)
163 if pred == _nil:
163 if pred == _nil:
164 dict_impl.__setattr__(self, 'lh', succ)
164 dict_impl.__setattr__(self, 'lh', succ)
165 else:
165 else:
166 dict_impl.__getitem__(self, pred)[2] = succ
166 dict_impl.__getitem__(self, pred)[2] = succ
167 if succ == _nil:
167 if succ == _nil:
168 dict_impl.__setattr__(self, 'lt', pred)
168 dict_impl.__setattr__(self, 'lt', pred)
169 else:
169 else:
170 dict_impl.__getitem__(self, succ)[0] = pred
170 dict_impl.__getitem__(self, succ)[0] = pred
171 dict_impl.__delitem__(self, key)
171 dict_impl.__delitem__(self, key)
172
172
173 def __contains__(self, key):
173 def __contains__(self, key):
174 return key in self.keys()
174 return key in self.keys()
175
175
176 def __len__(self):
176 def __len__(self):
177 return len(self.keys())
177 return len(self.keys())
178
178
179 def __str__(self):
179 def __str__(self):
180 pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
180 pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
181 return "{%s}" % ", ".join(pairs)
181 return "{%s}" % ", ".join(pairs)
182
182
183 def __repr__(self):
183 def __repr__(self):
184 if self:
184 if self:
185 pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
185 pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
186 return "odict([%s])" % ", ".join(pairs)
186 return "odict([%s])" % ", ".join(pairs)
187 else:
187 else:
188 return "odict()"
188 return "odict()"
189
189
190 def get(self, k, x=None):
190 def get(self, k, x=None):
191 if k in self:
191 if k in self:
192 return self._dict_impl().__getitem__(self, k)[1]
192 return self._dict_impl().__getitem__(self, k)[1]
193 else:
193 else:
194 return x
194 return x
195
195
196 def __iter__(self):
196 def __iter__(self):
197 dict_impl = self._dict_impl()
197 dict_impl = self._dict_impl()
198 curr_key = dict_impl.__getattribute__(self, 'lh')
198 curr_key = dict_impl.__getattribute__(self, 'lh')
199 while curr_key != _nil:
199 while curr_key != _nil:
200 yield curr_key
200 yield curr_key
201 curr_key = dict_impl.__getitem__(self, curr_key)[2]
201 curr_key = dict_impl.__getitem__(self, curr_key)[2]
202
202
203 iterkeys = __iter__
203 iterkeys = __iter__
204
204
205 def keys(self):
205 def keys(self):
206 return list(self.iterkeys())
206 return list(self.iterkeys())
207
207
208 def itervalues(self):
208 def itervalues(self):
209 dict_impl = self._dict_impl()
209 dict_impl = self._dict_impl()
210 curr_key = dict_impl.__getattribute__(self, 'lh')
210 curr_key = dict_impl.__getattribute__(self, 'lh')
211 while curr_key != _nil:
211 while curr_key != _nil:
212 _, val, curr_key = dict_impl.__getitem__(self, curr_key)
212 _, val, curr_key = dict_impl.__getitem__(self, curr_key)
213 yield val
213 yield val
214
214
215 def values(self):
215 def values(self):
216 return list(self.itervalues())
216 return list(self.itervalues())
217
217
218 def iteritems(self):
218 def iteritems(self):
219 dict_impl = self._dict_impl()
219 dict_impl = self._dict_impl()
220 curr_key = dict_impl.__getattribute__(self, 'lh')
220 curr_key = dict_impl.__getattribute__(self, 'lh')
221 while curr_key != _nil:
221 while curr_key != _nil:
222 _, val, next_key = dict_impl.__getitem__(self, curr_key)
222 _, val, next_key = dict_impl.__getitem__(self, curr_key)
223 yield curr_key, val
223 yield curr_key, val
224 curr_key = next_key
224 curr_key = next_key
225
225
226 def items(self):
226 def items(self):
227 return list(self.iteritems())
227 return list(self.iteritems())
228
228
229 def sort(self, cmp=None, key=None, reverse=False):
229 def sort(self, cmp=None, key=None, reverse=False):
230 items = [(k, v) for k, v in self.items()]
230 items = [(k, v) for k, v in self.items()]
231 if cmp is not None:
231 if cmp is not None:
232 items = sorted(items, cmp=cmp)
232 items = sorted(items, cmp=cmp)
233 elif key is not None:
233 elif key is not None:
234 items = sorted(items, key=key)
234 items = sorted(items, key=key)
235 else:
235 else:
236 items = sorted(items, key=lambda x: x[1])
236 items = sorted(items, key=lambda x: x[1])
237 if reverse:
237 if reverse:
238 items.reverse()
238 items.reverse()
239 self.clear()
239 self.clear()
240 self.__init__(items)
240 self.__init__(items)
241
241
242 def clear(self):
242 def clear(self):
243 dict_impl = self._dict_impl()
243 dict_impl = self._dict_impl()
244 dict_impl.clear(self)
244 dict_impl.clear(self)
245 dict_impl.__setattr__(self, 'lh', _nil)
245 dict_impl.__setattr__(self, 'lh', _nil)
246 dict_impl.__setattr__(self, 'lt', _nil)
246 dict_impl.__setattr__(self, 'lt', _nil)
247
247
248 def copy(self):
248 def copy(self):
249 return self.__class__(self)
249 return self.__class__(self)
250
250
251 def update(self, data=(), **kwds):
251 def update(self, data=(), **kwds):
252 if kwds:
252 if kwds:
253 raise TypeError("update() of ordered dict takes no keyword "
253 raise TypeError("update() of ordered dict takes no keyword "
254 "arguments to avoid an ordering trap.")
254 "arguments to avoid an ordering trap.")
255 if hasattr(data, "iteritems"):
255 if hasattr(data, "iteritems"):
256 data = data.iteritems()
256 data = data.iteritems()
257 for key, val in data:
257 for key, val in data:
258 self[key] = val
258 self[key] = val
259
259
260 def setdefault(self, k, x=None):
260 def setdefault(self, k, x=None):
261 try:
261 try:
262 return self[k]
262 return self[k]
263 except KeyError:
263 except KeyError:
264 self[k] = x
264 self[k] = x
265 return x
265 return x
266
266
267 def pop(self, k, x=_nil):
267 def pop(self, k, x=_nil):
268 try:
268 try:
269 val = self[k]
269 val = self[k]
270 del self[k]
270 del self[k]
271 return val
271 return val
272 except KeyError:
272 except KeyError:
273 if x == _nil:
273 if x == _nil:
274 raise
274 raise
275 return x
275 return x
276
276
277 def popitem(self):
277 def popitem(self):
278 try:
278 try:
279 dict_impl = self._dict_impl()
279 dict_impl = self._dict_impl()
280 key = dict_impl.__getattribute__(self, 'lt')
280 key = dict_impl.__getattribute__(self, 'lt')
281 return key, self.pop(key)
281 return key, self.pop(key)
282 except KeyError:
282 except KeyError:
283 raise KeyError("'popitem(): ordered dictionary is empty'")
283 raise KeyError("'popitem(): ordered dictionary is empty'")
284
284
285 def riterkeys(self):
285 def riterkeys(self):
286 """To iterate on keys in reversed order.
286 """To iterate on keys in reversed order.
287 """
287 """
288 dict_impl = self._dict_impl()
288 dict_impl = self._dict_impl()
289 curr_key = dict_impl.__getattribute__(self, 'lt')
289 curr_key = dict_impl.__getattribute__(self, 'lt')
290 while curr_key != _nil:
290 while curr_key != _nil:
291 yield curr_key
291 yield curr_key
292 curr_key = dict_impl.__getitem__(self, curr_key)[0]
292 curr_key = dict_impl.__getitem__(self, curr_key)[0]
293
293
294 __reversed__ = riterkeys
294 __reversed__ = riterkeys
295
295
296 def rkeys(self):
296 def rkeys(self):
297 """List of the keys in reversed order.
297 """List of the keys in reversed order.
298 """
298 """
299 return list(self.riterkeys())
299 return list(self.riterkeys())
300
300
301 def ritervalues(self):
301 def ritervalues(self):
302 """To iterate on values in reversed order.
302 """To iterate on values in reversed order.
303 """
303 """
304 dict_impl = self._dict_impl()
304 dict_impl = self._dict_impl()
305 curr_key = dict_impl.__getattribute__(self, 'lt')
305 curr_key = dict_impl.__getattribute__(self, 'lt')
306 while curr_key != _nil:
306 while curr_key != _nil:
307 curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
307 curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
308 yield val
308 yield val
309
309
310 def rvalues(self):
310 def rvalues(self):
311 """List of the values in reversed order.
311 """List of the values in reversed order.
312 """
312 """
313 return list(self.ritervalues())
313 return list(self.ritervalues())
314
314
315 def riteritems(self):
315 def riteritems(self):
316 """To iterate on (key, value) in reversed order.
316 """To iterate on (key, value) in reversed order.
317 """
317 """
318 dict_impl = self._dict_impl()
318 dict_impl = self._dict_impl()
319 curr_key = dict_impl.__getattribute__(self, 'lt')
319 curr_key = dict_impl.__getattribute__(self, 'lt')
320 while curr_key != _nil:
320 while curr_key != _nil:
321 pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
321 pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
322 yield curr_key, val
322 yield curr_key, val
323 curr_key = pred_key
323 curr_key = pred_key
324
324
325 def ritems(self):
325 def ritems(self):
326 """List of the (key, value) in reversed order.
326 """List of the (key, value) in reversed order.
327 """
327 """
328 return list(self.riteritems())
328 return list(self.riteritems())
329
329
330 def firstkey(self):
330 def firstkey(self):
331 if self:
331 if self:
332 return self._dict_impl().__getattribute__(self, 'lh')
332 return self._dict_impl().__getattribute__(self, 'lh')
333 else:
333 else:
334 raise KeyError("'firstkey(): ordered dictionary is empty'")
334 raise KeyError("'firstkey(): ordered dictionary is empty'")
335
335
336 def lastkey(self):
336 def lastkey(self):
337 if self:
337 if self:
338 return self._dict_impl().__getattribute__(self, 'lt')
338 return self._dict_impl().__getattribute__(self, 'lt')
339 else:
339 else:
340 raise KeyError("'lastkey(): ordered dictionary is empty'")
340 raise KeyError("'lastkey(): ordered dictionary is empty'")
341
341
342 def as_dict(self):
342 def as_dict(self):
343 return self._dict_impl()(self.items())
343 return self._dict_impl()(self.items())
344
344
345 def _repr(self):
345 def _repr(self):
346 """_repr(): low level repr of the whole data contained in the odict.
346 """_repr(): low level repr of the whole data contained in the odict.
347 Useful for debugging.
347 Useful for debugging.
348 """
348 """
349 dict_impl = self._dict_impl()
349 dict_impl = self._dict_impl()
350 form = "odict low level repr lh,lt,data: %r, %r, %s"
350 form = "odict low level repr lh,lt,data: %r, %r, %s"
351 return form % (dict_impl.__getattribute__(self, 'lh'),
351 return form % (dict_impl.__getattribute__(self, 'lh'),
352 dict_impl.__getattribute__(self, 'lt'),
352 dict_impl.__getattribute__(self, 'lt'),
353 dict_impl.__repr__(self))
353 dict_impl.__repr__(self))
354
354
355
355
356 class OrderedDict(_odict, dict):
356 class OrderedDict(_odict, dict):
357
357
358 def _dict_impl(self):
358 def _dict_impl(self):
359 return dict
359 return dict
360
360
361
361
362 #==============================================================================
362 #==============================================================================
363 # OrderedSet
363 # OrderedSet
364 #==============================================================================
364 #==============================================================================
365 from sqlalchemy.util import OrderedSet
365 from sqlalchemy.util import OrderedSet
366
366
367
367
368 #==============================================================================
368 #==============================================================================
369 # kill FUNCTIONS
369 # kill FUNCTIONS
370 #==============================================================================
370 #==============================================================================
371 if __platform__ in PLATFORM_WIN:
371 if __platform__ in PLATFORM_WIN:
372 import ctypes
372 import ctypes
373
373
374 def kill(pid, sig):
374 def kill(pid, sig):
375 """kill function for Win32"""
375 """kill function for Win32"""
376 kernel32 = ctypes.windll.kernel32
376 kernel32 = ctypes.windll.kernel32
377 handle = kernel32.OpenProcess(1, 0, pid)
377 handle = kernel32.OpenProcess(1, 0, pid)
378 return (0 != kernel32.TerminateProcess(handle, 0))
378 return (0 != kernel32.TerminateProcess(handle, 0))
379
379
380 else:
380 else:
381 kill = os.kill
381 kill = os.kill
382
383
384 #==============================================================================
385 # itertools.product
386 #==============================================================================
387
388 try:
389 from itertools import product
390 except ImportError:
391 def product(*args, **kwds):
392 # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
393 # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
394 pools = map(tuple, args) * kwds.get('repeat', 1)
395 result = [[]]
396 for pool in pools:
397 result = [x + [y] for x in result for y in pool]
398 for prod in result:
399 yield tuple(prod)
@@ -1,622 +1,629 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or 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 re
27 import logging
28 import logging
28 import datetime
29 import datetime
29 import traceback
30 import traceback
30 import paste
31 import paste
31 import beaker
32 import beaker
32 import tarfile
33 import tarfile
33 import shutil
34 import shutil
34 from os.path import abspath
35 from os.path import abspath
35 from os.path import dirname as dn, join as jn
36 from os.path import dirname as dn, join as jn
36
37
37 from paste.script.command import Command, BadCommand
38 from paste.script.command import Command, BadCommand
38
39
39 from mercurial import ui, config
40 from mercurial import ui, config
40
41
41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42
43
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.helpers import get_scm
47 from rhodecode.lib.vcs.utils.helpers import get_scm
47 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.exceptions import VCSError
48
49
49 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
50
51
51 from rhodecode.model import meta
52 from rhodecode.model import meta
52 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
54 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
55 from rhodecode.model.repos_group import ReposGroupModel
56 from rhodecode.model.repos_group import ReposGroupModel
56
57
57 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
58
59
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61
59
62
60 def recursive_replace(str_, replace=' '):
63 def recursive_replace(str_, replace=' '):
61 """Recursive replace of given sign to just one instance
64 """Recursive replace of given sign to just one instance
62
65
63 :param str_: given string
66 :param str_: given string
64 :param replace: char to find and replace multiple instances
67 :param replace: char to find and replace multiple instances
65
68
66 Examples::
69 Examples::
67 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
70 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
68 'Mighty-Mighty-Bo-sstones'
71 'Mighty-Mighty-Bo-sstones'
69 """
72 """
70
73
71 if str_.find(replace * 2) == -1:
74 if str_.find(replace * 2) == -1:
72 return str_
75 return str_
73 else:
76 else:
74 str_ = str_.replace(replace * 2, replace)
77 str_ = str_.replace(replace * 2, replace)
75 return recursive_replace(str_, replace)
78 return recursive_replace(str_, replace)
76
79
77
80
78 def repo_name_slug(value):
81 def repo_name_slug(value):
79 """Return slug of name of repository
82 """Return slug of name of repository
80 This function is called on each creation/modification
83 This function is called on each creation/modification
81 of repository to prevent bad names in repo
84 of repository to prevent bad names in repo
82 """
85 """
83
86
84 slug = remove_formatting(value)
87 slug = remove_formatting(value)
85 slug = strip_tags(slug)
88 slug = strip_tags(slug)
86
89
87 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
90 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
88 slug = slug.replace(c, '-')
91 slug = slug.replace(c, '-')
89 slug = recursive_replace(slug, '-')
92 slug = recursive_replace(slug, '-')
90 slug = collapse(slug, '-')
93 slug = collapse(slug, '-')
91 return slug
94 return slug
92
95
93
96
94 def get_repo_slug(request):
97 def get_repo_slug(request):
95 _repo = request.environ['pylons.routes_dict'].get('repo_name')
98 _repo = request.environ['pylons.routes_dict'].get('repo_name')
96 if _repo:
99 if _repo:
97 _repo = _repo.rstrip('/')
100 _repo = _repo.rstrip('/')
98 return _repo
101 return _repo
99
102
100
103
101 def get_repos_group_slug(request):
104 def get_repos_group_slug(request):
102 _group = request.environ['pylons.routes_dict'].get('group_name')
105 _group = request.environ['pylons.routes_dict'].get('group_name')
103 if _group:
106 if _group:
104 _group = _group.rstrip('/')
107 _group = _group.rstrip('/')
105 return _group
108 return _group
106
109
107
110
108 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
111 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
109 """
112 """
110 Action logger for various actions made by users
113 Action logger for various actions made by users
111
114
112 :param user: user that made this action, can be a unique username string or
115 :param user: user that made this action, can be a unique username string or
113 object containing user_id attribute
116 object containing user_id attribute
114 :param action: action to log, should be on of predefined unique actions for
117 :param action: action to log, should be on of predefined unique actions for
115 easy translations
118 easy translations
116 :param repo: string name of repository or object containing repo_id,
119 :param repo: string name of repository or object containing repo_id,
117 that action was made on
120 that action was made on
118 :param ipaddr: optional ip address from what the action was made
121 :param ipaddr: optional ip address from what the action was made
119 :param sa: optional sqlalchemy session
122 :param sa: optional sqlalchemy session
120
123
121 """
124 """
122
125
123 if not sa:
126 if not sa:
124 sa = meta.Session
127 sa = meta.Session
125
128
126 try:
129 try:
127 if hasattr(user, 'user_id'):
130 if hasattr(user, 'user_id'):
128 user_obj = user
131 user_obj = user
129 elif isinstance(user, basestring):
132 elif isinstance(user, basestring):
130 user_obj = User.get_by_username(user)
133 user_obj = User.get_by_username(user)
131 else:
134 else:
132 raise Exception('You have to provide user object or username')
135 raise Exception('You have to provide user object or username')
133
136
134 if hasattr(repo, 'repo_id'):
137 if hasattr(repo, 'repo_id'):
135 repo_obj = Repository.get(repo.repo_id)
138 repo_obj = Repository.get(repo.repo_id)
136 repo_name = repo_obj.repo_name
139 repo_name = repo_obj.repo_name
137 elif isinstance(repo, basestring):
140 elif isinstance(repo, basestring):
138 repo_name = repo.lstrip('/')
141 repo_name = repo.lstrip('/')
139 repo_obj = Repository.get_by_repo_name(repo_name)
142 repo_obj = Repository.get_by_repo_name(repo_name)
140 else:
143 else:
141 raise Exception('You have to provide repository to action logger')
144 raise Exception('You have to provide repository to action logger')
142
145
143 user_log = UserLog()
146 user_log = UserLog()
144 user_log.user_id = user_obj.user_id
147 user_log.user_id = user_obj.user_id
145 user_log.action = action
148 user_log.action = action
146
149
147 user_log.repository_id = repo_obj.repo_id
150 user_log.repository_id = repo_obj.repo_id
148 user_log.repository_name = repo_name
151 user_log.repository_name = repo_name
149
152
150 user_log.action_date = datetime.datetime.now()
153 user_log.action_date = datetime.datetime.now()
151 user_log.user_ip = ipaddr
154 user_log.user_ip = ipaddr
152 sa.add(user_log)
155 sa.add(user_log)
153
156
154 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
157 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
155 if commit:
158 if commit:
156 sa.commit()
159 sa.commit()
157 except:
160 except:
158 log.error(traceback.format_exc())
161 log.error(traceback.format_exc())
159 raise
162 raise
160
163
161
164
162 def get_repos(path, recursive=False):
165 def get_repos(path, recursive=False):
163 """
166 """
164 Scans given path for repos and return (name,(type,path)) tuple
167 Scans given path for repos and return (name,(type,path)) tuple
165
168
166 :param path: path to scan for repositories
169 :param path: path to scan for repositories
167 :param recursive: recursive search and return names with subdirs in front
170 :param recursive: recursive search and return names with subdirs in front
168 """
171 """
169
172
170 # remove ending slash for better results
173 # remove ending slash for better results
171 path = path.rstrip(os.sep)
174 path = path.rstrip(os.sep)
172
175
173 def _get_repos(p):
176 def _get_repos(p):
174 if not os.access(p, os.W_OK):
177 if not os.access(p, os.W_OK):
175 return
178 return
176 for dirpath in os.listdir(p):
179 for dirpath in os.listdir(p):
177 if os.path.isfile(os.path.join(p, dirpath)):
180 if os.path.isfile(os.path.join(p, dirpath)):
178 continue
181 continue
179 cur_path = os.path.join(p, dirpath)
182 cur_path = os.path.join(p, dirpath)
180 try:
183 try:
181 scm_info = get_scm(cur_path)
184 scm_info = get_scm(cur_path)
182 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
185 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
183 except VCSError:
186 except VCSError:
184 if not recursive:
187 if not recursive:
185 continue
188 continue
186 #check if this dir containts other repos for recursive scan
189 #check if this dir containts other repos for recursive scan
187 rec_path = os.path.join(p, dirpath)
190 rec_path = os.path.join(p, dirpath)
188 if os.path.isdir(rec_path):
191 if os.path.isdir(rec_path):
189 for inner_scm in _get_repos(rec_path):
192 for inner_scm in _get_repos(rec_path):
190 yield inner_scm
193 yield inner_scm
191
194
192 return _get_repos(path)
195 return _get_repos(path)
193
196
194
197
195 def is_valid_repo(repo_name, base_path):
198 def is_valid_repo(repo_name, base_path):
196 """
199 """
197 Returns True if given path is a valid repository False otherwise
200 Returns True if given path is a valid repository False otherwise
198 :param repo_name:
201 :param repo_name:
199 :param base_path:
202 :param base_path:
200
203
201 :return True: if given path is a valid repository
204 :return True: if given path is a valid repository
202 """
205 """
203 full_path = os.path.join(base_path, repo_name)
206 full_path = os.path.join(base_path, repo_name)
204
207
205 try:
208 try:
206 get_scm(full_path)
209 get_scm(full_path)
207 return True
210 return True
208 except VCSError:
211 except VCSError:
209 return False
212 return False
210
213
211
214
212 def is_valid_repos_group(repos_group_name, base_path):
215 def is_valid_repos_group(repos_group_name, base_path):
213 """
216 """
214 Returns True if given path is a repos group False otherwise
217 Returns True if given path is a repos group False otherwise
215
218
216 :param repo_name:
219 :param repo_name:
217 :param base_path:
220 :param base_path:
218 """
221 """
219 full_path = os.path.join(base_path, repos_group_name)
222 full_path = os.path.join(base_path, repos_group_name)
220
223
221 # check if it's not a repo
224 # check if it's not a repo
222 if is_valid_repo(repos_group_name, base_path):
225 if is_valid_repo(repos_group_name, base_path):
223 return False
226 return False
224
227
225 # check if it's a valid path
228 # check if it's a valid path
226 if os.path.isdir(full_path):
229 if os.path.isdir(full_path):
227 return True
230 return True
228
231
229 return False
232 return False
230
233
231
234
232 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
235 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
233 while True:
236 while True:
234 ok = raw_input(prompt)
237 ok = raw_input(prompt)
235 if ok in ('y', 'ye', 'yes'):
238 if ok in ('y', 'ye', 'yes'):
236 return True
239 return True
237 if ok in ('n', 'no', 'nop', 'nope'):
240 if ok in ('n', 'no', 'nop', 'nope'):
238 return False
241 return False
239 retries = retries - 1
242 retries = retries - 1
240 if retries < 0:
243 if retries < 0:
241 raise IOError
244 raise IOError
242 print complaint
245 print complaint
243
246
244 #propagated from mercurial documentation
247 #propagated from mercurial documentation
245 ui_sections = ['alias', 'auth',
248 ui_sections = ['alias', 'auth',
246 'decode/encode', 'defaults',
249 'decode/encode', 'defaults',
247 'diff', 'email',
250 'diff', 'email',
248 'extensions', 'format',
251 'extensions', 'format',
249 'merge-patterns', 'merge-tools',
252 'merge-patterns', 'merge-tools',
250 'hooks', 'http_proxy',
253 'hooks', 'http_proxy',
251 'smtp', 'patch',
254 'smtp', 'patch',
252 'paths', 'profiling',
255 'paths', 'profiling',
253 'server', 'trusted',
256 'server', 'trusted',
254 'ui', 'web', ]
257 'ui', 'web', ]
255
258
256
259
257 def make_ui(read_from='file', path=None, checkpaths=True):
260 def make_ui(read_from='file', path=None, checkpaths=True):
258 """A function that will read python rc files or database
261 """A function that will read python rc files or database
259 and make an mercurial ui object from read options
262 and make an mercurial ui object from read options
260
263
261 :param path: path to mercurial config file
264 :param path: path to mercurial config file
262 :param checkpaths: check the path
265 :param checkpaths: check the path
263 :param read_from: read from 'file' or 'db'
266 :param read_from: read from 'file' or 'db'
264 """
267 """
265
268
266 baseui = ui.ui()
269 baseui = ui.ui()
267
270
268 # clean the baseui object
271 # clean the baseui object
269 baseui._ocfg = config.config()
272 baseui._ocfg = config.config()
270 baseui._ucfg = config.config()
273 baseui._ucfg = config.config()
271 baseui._tcfg = config.config()
274 baseui._tcfg = config.config()
272
275
273 if read_from == 'file':
276 if read_from == 'file':
274 if not os.path.isfile(path):
277 if not os.path.isfile(path):
275 log.debug('hgrc file is not present at %s skipping...' % path)
278 log.debug('hgrc file is not present at %s skipping...' % path)
276 return False
279 return False
277 log.debug('reading hgrc from %s' % path)
280 log.debug('reading hgrc from %s' % path)
278 cfg = config.config()
281 cfg = config.config()
279 cfg.read(path)
282 cfg.read(path)
280 for section in ui_sections:
283 for section in ui_sections:
281 for k, v in cfg.items(section):
284 for k, v in cfg.items(section):
282 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
285 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
283 baseui.setconfig(section, k, v)
286 baseui.setconfig(section, k, v)
284
287
285 elif read_from == 'db':
288 elif read_from == 'db':
286 sa = meta.Session
289 sa = meta.Session
287 ret = sa.query(RhodeCodeUi)\
290 ret = sa.query(RhodeCodeUi)\
288 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
291 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
289 .all()
292 .all()
290
293
291 hg_ui = ret
294 hg_ui = ret
292 for ui_ in hg_ui:
295 for ui_ in hg_ui:
293 if ui_.ui_active:
296 if ui_.ui_active:
294 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
297 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
295 ui_.ui_key, ui_.ui_value)
298 ui_.ui_key, ui_.ui_value)
296 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
299 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
297
300
298 meta.Session.remove()
301 meta.Session.remove()
299 return baseui
302 return baseui
300
303
301
304
302 def set_rhodecode_config(config):
305 def set_rhodecode_config(config):
303 """
306 """
304 Updates pylons config with new settings from database
307 Updates pylons config with new settings from database
305
308
306 :param config:
309 :param config:
307 """
310 """
308 hgsettings = RhodeCodeSetting.get_app_settings()
311 hgsettings = RhodeCodeSetting.get_app_settings()
309
312
310 for k, v in hgsettings.items():
313 for k, v in hgsettings.items():
311 config[k] = v
314 config[k] = v
312
315
313
316
314 def invalidate_cache(cache_key, *args):
317 def invalidate_cache(cache_key, *args):
315 """
318 """
316 Puts cache invalidation task into db for
319 Puts cache invalidation task into db for
317 further global cache invalidation
320 further global cache invalidation
318 """
321 """
319
322
320 from rhodecode.model.scm import ScmModel
323 from rhodecode.model.scm import ScmModel
321
324
322 if cache_key.startswith('get_repo_cached_'):
325 if cache_key.startswith('get_repo_cached_'):
323 name = cache_key.split('get_repo_cached_')[-1]
326 name = cache_key.split('get_repo_cached_')[-1]
324 ScmModel().mark_for_invalidation(name)
327 ScmModel().mark_for_invalidation(name)
325
328
326
329
327 class EmptyChangeset(BaseChangeset):
330 class EmptyChangeset(BaseChangeset):
328 """
331 """
329 An dummy empty changeset. It's possible to pass hash when creating
332 An dummy empty changeset. It's possible to pass hash when creating
330 an EmptyChangeset
333 an EmptyChangeset
331 """
334 """
332
335
333 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
336 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
334 alias=None):
337 alias=None):
335 self._empty_cs = cs
338 self._empty_cs = cs
336 self.revision = -1
339 self.revision = -1
337 self.message = ''
340 self.message = ''
338 self.author = ''
341 self.author = ''
339 self.date = ''
342 self.date = ''
340 self.repository = repo
343 self.repository = repo
341 self.requested_revision = requested_revision
344 self.requested_revision = requested_revision
342 self.alias = alias
345 self.alias = alias
343
346
344 @LazyProperty
347 @LazyProperty
345 def raw_id(self):
348 def raw_id(self):
346 """
349 """
347 Returns raw string identifying this changeset, useful for web
350 Returns raw string identifying this changeset, useful for web
348 representation.
351 representation.
349 """
352 """
350
353
351 return self._empty_cs
354 return self._empty_cs
352
355
353 @LazyProperty
356 @LazyProperty
354 def branch(self):
357 def branch(self):
355 return get_backend(self.alias).DEFAULT_BRANCH_NAME
358 return get_backend(self.alias).DEFAULT_BRANCH_NAME
356
359
357 @LazyProperty
360 @LazyProperty
358 def short_id(self):
361 def short_id(self):
359 return self.raw_id[:12]
362 return self.raw_id[:12]
360
363
361 def get_file_changeset(self, path):
364 def get_file_changeset(self, path):
362 return self
365 return self
363
366
364 def get_file_content(self, path):
367 def get_file_content(self, path):
365 return u''
368 return u''
366
369
367 def get_file_size(self, path):
370 def get_file_size(self, path):
368 return 0
371 return 0
369
372
370
373
371 def map_groups(groups):
374 def map_groups(groups):
372 """
375 """
373 Checks for groups existence, and creates groups structures.
376 Checks for groups existence, and creates groups structures.
374 It returns last group in structure
377 It returns last group in structure
375
378
376 :param groups: list of groups structure
379 :param groups: list of groups structure
377 """
380 """
378 sa = meta.Session
381 sa = meta.Session
379
382
380 parent = None
383 parent = None
381 group = None
384 group = None
382
385
383 # last element is repo in nested groups structure
386 # last element is repo in nested groups structure
384 groups = groups[:-1]
387 groups = groups[:-1]
385 rgm = ReposGroupModel(sa)
388 rgm = ReposGroupModel(sa)
386 for lvl, group_name in enumerate(groups):
389 for lvl, group_name in enumerate(groups):
387 group_name = '/'.join(groups[:lvl] + [group_name])
390 group_name = '/'.join(groups[:lvl] + [group_name])
388 group = RepoGroup.get_by_group_name(group_name)
391 group = RepoGroup.get_by_group_name(group_name)
389 desc = '%s group' % group_name
392 desc = '%s group' % group_name
390
393
391 # # WTF that doesn't work !?
394 # # WTF that doesn't work !?
392 # if group is None:
395 # if group is None:
393 # group = rgm.create(group_name, desc, parent, just_db=True)
396 # group = rgm.create(group_name, desc, parent, just_db=True)
394 # sa.commit()
397 # sa.commit()
395
398
399 # skip folders that are now removed repos
400 if REMOVED_REPO_PAT.match(group_name):
401 break
402
396 if group is None:
403 if group is None:
397 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
404 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
398 group = RepoGroup(group_name, parent)
405 group = RepoGroup(group_name, parent)
399 group.group_description = desc
406 group.group_description = desc
400 sa.add(group)
407 sa.add(group)
401 rgm._create_default_perms(group)
408 rgm._create_default_perms(group)
402 sa.commit()
409 sa.commit()
403 parent = group
410 parent = group
404 return group
411 return group
405
412
406
413
407 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
414 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
408 """
415 """
409 maps all repos given in initial_repo_list, non existing repositories
416 maps all repos given in initial_repo_list, non existing repositories
410 are created, if remove_obsolete is True it also check for db entries
417 are created, if remove_obsolete is True it also check for db entries
411 that are not in initial_repo_list and removes them.
418 that are not in initial_repo_list and removes them.
412
419
413 :param initial_repo_list: list of repositories found by scanning methods
420 :param initial_repo_list: list of repositories found by scanning methods
414 :param remove_obsolete: check for obsolete entries in database
421 :param remove_obsolete: check for obsolete entries in database
415 """
422 """
416 from rhodecode.model.repo import RepoModel
423 from rhodecode.model.repo import RepoModel
417 sa = meta.Session
424 sa = meta.Session
418 rm = RepoModel()
425 rm = RepoModel()
419 user = sa.query(User).filter(User.admin == True).first()
426 user = sa.query(User).filter(User.admin == True).first()
420 if user is None:
427 if user is None:
421 raise Exception('Missing administrative account !')
428 raise Exception('Missing administrative account !')
422 added = []
429 added = []
423
430
424 for name, repo in initial_repo_list.items():
431 for name, repo in initial_repo_list.items():
425 group = map_groups(name.split(Repository.url_sep()))
432 group = map_groups(name.split(Repository.url_sep()))
426 if not rm.get_by_repo_name(name, cache=False):
433 if not rm.get_by_repo_name(name, cache=False):
427 log.info('repository %s not found creating default' % name)
434 log.info('repository %s not found creating default' % name)
428 added.append(name)
435 added.append(name)
429 form_data = {
436 form_data = {
430 'repo_name': name,
437 'repo_name': name,
431 'repo_name_full': name,
438 'repo_name_full': name,
432 'repo_type': repo.alias,
439 'repo_type': repo.alias,
433 'description': repo.description \
440 'description': repo.description \
434 if repo.description != 'unknown' else '%s repository' % name,
441 if repo.description != 'unknown' else '%s repository' % name,
435 'private': False,
442 'private': False,
436 'group_id': getattr(group, 'group_id', None)
443 'group_id': getattr(group, 'group_id', None)
437 }
444 }
438 rm.create(form_data, user, just_db=True)
445 rm.create(form_data, user, just_db=True)
439 sa.commit()
446 sa.commit()
440 removed = []
447 removed = []
441 if remove_obsolete:
448 if remove_obsolete:
442 #remove from database those repositories that are not in the filesystem
449 #remove from database those repositories that are not in the filesystem
443 for repo in sa.query(Repository).all():
450 for repo in sa.query(Repository).all():
444 if repo.repo_name not in initial_repo_list.keys():
451 if repo.repo_name not in initial_repo_list.keys():
445 removed.append(repo.repo_name)
452 removed.append(repo.repo_name)
446 sa.delete(repo)
453 sa.delete(repo)
447 sa.commit()
454 sa.commit()
448
455
449 return added, removed
456 return added, removed
450
457
451
458
452 # set cache regions for beaker so celery can utilise it
459 # set cache regions for beaker so celery can utilise it
453 def add_cache(settings):
460 def add_cache(settings):
454 cache_settings = {'regions': None}
461 cache_settings = {'regions': None}
455 for key in settings.keys():
462 for key in settings.keys():
456 for prefix in ['beaker.cache.', 'cache.']:
463 for prefix in ['beaker.cache.', 'cache.']:
457 if key.startswith(prefix):
464 if key.startswith(prefix):
458 name = key.split(prefix)[1].strip()
465 name = key.split(prefix)[1].strip()
459 cache_settings[name] = settings[key].strip()
466 cache_settings[name] = settings[key].strip()
460 if cache_settings['regions']:
467 if cache_settings['regions']:
461 for region in cache_settings['regions'].split(','):
468 for region in cache_settings['regions'].split(','):
462 region = region.strip()
469 region = region.strip()
463 region_settings = {}
470 region_settings = {}
464 for key, value in cache_settings.items():
471 for key, value in cache_settings.items():
465 if key.startswith(region):
472 if key.startswith(region):
466 region_settings[key.split('.')[1]] = value
473 region_settings[key.split('.')[1]] = value
467 region_settings['expire'] = int(region_settings.get('expire',
474 region_settings['expire'] = int(region_settings.get('expire',
468 60))
475 60))
469 region_settings.setdefault('lock_dir',
476 region_settings.setdefault('lock_dir',
470 cache_settings.get('lock_dir'))
477 cache_settings.get('lock_dir'))
471 region_settings.setdefault('data_dir',
478 region_settings.setdefault('data_dir',
472 cache_settings.get('data_dir'))
479 cache_settings.get('data_dir'))
473
480
474 if 'type' not in region_settings:
481 if 'type' not in region_settings:
475 region_settings['type'] = cache_settings.get('type',
482 region_settings['type'] = cache_settings.get('type',
476 'memory')
483 'memory')
477 beaker.cache.cache_regions[region] = region_settings
484 beaker.cache.cache_regions[region] = region_settings
478
485
479
486
480 #==============================================================================
487 #==============================================================================
481 # TEST FUNCTIONS AND CREATORS
488 # TEST FUNCTIONS AND CREATORS
482 #==============================================================================
489 #==============================================================================
483 def create_test_index(repo_location, config, full_index):
490 def create_test_index(repo_location, config, full_index):
484 """
491 """
485 Makes default test index
492 Makes default test index
486
493
487 :param config: test config
494 :param config: test config
488 :param full_index:
495 :param full_index:
489 """
496 """
490
497
491 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
498 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
492 from rhodecode.lib.pidlock import DaemonLock, LockHeld
499 from rhodecode.lib.pidlock import DaemonLock, LockHeld
493
500
494 repo_location = repo_location
501 repo_location = repo_location
495
502
496 index_location = os.path.join(config['app_conf']['index_dir'])
503 index_location = os.path.join(config['app_conf']['index_dir'])
497 if not os.path.exists(index_location):
504 if not os.path.exists(index_location):
498 os.makedirs(index_location)
505 os.makedirs(index_location)
499
506
500 try:
507 try:
501 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
508 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
502 WhooshIndexingDaemon(index_location=index_location,
509 WhooshIndexingDaemon(index_location=index_location,
503 repo_location=repo_location)\
510 repo_location=repo_location)\
504 .run(full_index=full_index)
511 .run(full_index=full_index)
505 l.release()
512 l.release()
506 except LockHeld:
513 except LockHeld:
507 pass
514 pass
508
515
509
516
510 def create_test_env(repos_test_path, config):
517 def create_test_env(repos_test_path, config):
511 """
518 """
512 Makes a fresh database and
519 Makes a fresh database and
513 install test repository into tmp dir
520 install test repository into tmp dir
514 """
521 """
515 from rhodecode.lib.db_manage import DbManage
522 from rhodecode.lib.db_manage import DbManage
516 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
523 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
517
524
518 # PART ONE create db
525 # PART ONE create db
519 dbconf = config['sqlalchemy.db1.url']
526 dbconf = config['sqlalchemy.db1.url']
520 log.debug('making test db %s' % dbconf)
527 log.debug('making test db %s' % dbconf)
521
528
522 # create test dir if it doesn't exist
529 # create test dir if it doesn't exist
523 if not os.path.isdir(repos_test_path):
530 if not os.path.isdir(repos_test_path):
524 log.debug('Creating testdir %s' % repos_test_path)
531 log.debug('Creating testdir %s' % repos_test_path)
525 os.makedirs(repos_test_path)
532 os.makedirs(repos_test_path)
526
533
527 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
534 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
528 tests=True)
535 tests=True)
529 dbmanage.create_tables(override=True)
536 dbmanage.create_tables(override=True)
530 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
537 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
531 dbmanage.create_default_user()
538 dbmanage.create_default_user()
532 dbmanage.admin_prompt()
539 dbmanage.admin_prompt()
533 dbmanage.create_permissions()
540 dbmanage.create_permissions()
534 dbmanage.populate_default_permissions()
541 dbmanage.populate_default_permissions()
535 Session.commit()
542 Session.commit()
536 # PART TWO make test repo
543 # PART TWO make test repo
537 log.debug('making test vcs repositories')
544 log.debug('making test vcs repositories')
538
545
539 idx_path = config['app_conf']['index_dir']
546 idx_path = config['app_conf']['index_dir']
540 data_path = config['app_conf']['cache_dir']
547 data_path = config['app_conf']['cache_dir']
541
548
542 #clean index and data
549 #clean index and data
543 if idx_path and os.path.exists(idx_path):
550 if idx_path and os.path.exists(idx_path):
544 log.debug('remove %s' % idx_path)
551 log.debug('remove %s' % idx_path)
545 shutil.rmtree(idx_path)
552 shutil.rmtree(idx_path)
546
553
547 if data_path and os.path.exists(data_path):
554 if data_path and os.path.exists(data_path):
548 log.debug('remove %s' % data_path)
555 log.debug('remove %s' % data_path)
549 shutil.rmtree(data_path)
556 shutil.rmtree(data_path)
550
557
551 #CREATE DEFAULT HG REPOSITORY
558 #CREATE DEFAULT HG REPOSITORY
552 cur_dir = dn(dn(abspath(__file__)))
559 cur_dir = dn(dn(abspath(__file__)))
553 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
560 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
554 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
561 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
555 tar.close()
562 tar.close()
556
563
557
564
558 #==============================================================================
565 #==============================================================================
559 # PASTER COMMANDS
566 # PASTER COMMANDS
560 #==============================================================================
567 #==============================================================================
561 class BasePasterCommand(Command):
568 class BasePasterCommand(Command):
562 """
569 """
563 Abstract Base Class for paster commands.
570 Abstract Base Class for paster commands.
564
571
565 The celery commands are somewhat aggressive about loading
572 The celery commands are somewhat aggressive about loading
566 celery.conf, and since our module sets the `CELERY_LOADER`
573 celery.conf, and since our module sets the `CELERY_LOADER`
567 environment variable to our loader, we have to bootstrap a bit and
574 environment variable to our loader, we have to bootstrap a bit and
568 make sure we've had a chance to load the pylons config off of the
575 make sure we've had a chance to load the pylons config off of the
569 command line, otherwise everything fails.
576 command line, otherwise everything fails.
570 """
577 """
571 min_args = 1
578 min_args = 1
572 min_args_error = "Please provide a paster config file as an argument."
579 min_args_error = "Please provide a paster config file as an argument."
573 takes_config_file = 1
580 takes_config_file = 1
574 requires_config_file = True
581 requires_config_file = True
575
582
576 def notify_msg(self, msg, log=False):
583 def notify_msg(self, msg, log=False):
577 """Make a notification to user, additionally if logger is passed
584 """Make a notification to user, additionally if logger is passed
578 it logs this action using given logger
585 it logs this action using given logger
579
586
580 :param msg: message that will be printed to user
587 :param msg: message that will be printed to user
581 :param log: logging instance, to use to additionally log this message
588 :param log: logging instance, to use to additionally log this message
582
589
583 """
590 """
584 if log and isinstance(log, logging):
591 if log and isinstance(log, logging):
585 log(msg)
592 log(msg)
586
593
587 def run(self, args):
594 def run(self, args):
588 """
595 """
589 Overrides Command.run
596 Overrides Command.run
590
597
591 Checks for a config file argument and loads it.
598 Checks for a config file argument and loads it.
592 """
599 """
593 if len(args) < self.min_args:
600 if len(args) < self.min_args:
594 raise BadCommand(
601 raise BadCommand(
595 self.min_args_error % {'min_args': self.min_args,
602 self.min_args_error % {'min_args': self.min_args,
596 'actual_args': len(args)})
603 'actual_args': len(args)})
597
604
598 # Decrement because we're going to lob off the first argument.
605 # Decrement because we're going to lob off the first argument.
599 # @@ This is hacky
606 # @@ This is hacky
600 self.min_args -= 1
607 self.min_args -= 1
601 self.bootstrap_config(args[0])
608 self.bootstrap_config(args[0])
602 self.update_parser()
609 self.update_parser()
603 return super(BasePasterCommand, self).run(args[1:])
610 return super(BasePasterCommand, self).run(args[1:])
604
611
605 def update_parser(self):
612 def update_parser(self):
606 """
613 """
607 Abstract method. Allows for the class's parser to be updated
614 Abstract method. Allows for the class's parser to be updated
608 before the superclass's `run` method is called. Necessary to
615 before the superclass's `run` method is called. Necessary to
609 allow options/arguments to be passed through to the underlying
616 allow options/arguments to be passed through to the underlying
610 celery command.
617 celery command.
611 """
618 """
612 raise NotImplementedError("Abstract Method.")
619 raise NotImplementedError("Abstract Method.")
613
620
614 def bootstrap_config(self, conf):
621 def bootstrap_config(self, conf):
615 """
622 """
616 Loads the pylons configuration.
623 Loads the pylons configuration.
617 """
624 """
618 from pylons import config as pylonsconfig
625 from pylons import config as pylonsconfig
619
626
620 path_to_ini_file = os.path.realpath(conf)
627 path_to_ini_file = os.path.realpath(conf)
621 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
628 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
622 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
629 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,773 +1,773 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.config.routing import ADMIN_PREFIX
35 from rhodecode.config.routing import ADMIN_PREFIX
36 from rhodecode.lib.utils import repo_name_slug
36 from rhodecode.lib.utils import repo_name_slug
37 from rhodecode.lib.auth import authenticate, get_crypt_password
37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.lib.exceptions import LdapImportError
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 from rhodecode import BACKENDS
40 from rhodecode import BACKENDS
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
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 #==============================================================================
50 #==============================================================================
51 # VALIDATORS
51 # VALIDATORS
52 #==============================================================================
52 #==============================================================================
53 class ValidAuthToken(formencode.validators.FancyValidator):
53 class ValidAuthToken(formencode.validators.FancyValidator):
54 messages = {'invalid_token': _('Token mismatch')}
54 messages = {'invalid_token': _('Token mismatch')}
55
55
56 def validate_python(self, value, state):
56 def validate_python(self, value, state):
57
57
58 if value != authentication_token():
58 if value != authentication_token():
59 raise formencode.Invalid(
59 raise formencode.Invalid(
60 self.message('invalid_token',
60 self.message('invalid_token',
61 state, search_number=value),
61 state, search_number=value),
62 value,
62 value,
63 state
63 state
64 )
64 )
65
65
66
66
67 def ValidUsername(edit, old_data):
67 def ValidUsername(edit, old_data):
68 class _ValidUsername(formencode.validators.FancyValidator):
68 class _ValidUsername(formencode.validators.FancyValidator):
69
69
70 def validate_python(self, value, state):
70 def validate_python(self, value, state):
71 if value in ['default', 'new_user']:
71 if value in ['default', 'new_user']:
72 raise formencode.Invalid(_('Invalid username'), value, state)
72 raise formencode.Invalid(_('Invalid username'), value, state)
73 #check if user is unique
73 #check if user is unique
74 old_un = None
74 old_un = None
75 if edit:
75 if edit:
76 old_un = User.get(old_data.get('user_id')).username
76 old_un = User.get(old_data.get('user_id')).username
77
77
78 if old_un != value or not edit:
78 if old_un != value or not edit:
79 if User.get_by_username(value, case_insensitive=True):
79 if User.get_by_username(value, case_insensitive=True):
80 raise formencode.Invalid(_('This username already '
80 raise formencode.Invalid(_('This username already '
81 'exists') , value, state)
81 'exists') , value, state)
82
82
83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
84 raise formencode.Invalid(
84 raise formencode.Invalid(
85 _('Username may only contain alphanumeric characters '
85 _('Username may only contain alphanumeric characters '
86 'underscores, periods or dashes and must begin with '
86 'underscores, periods or dashes and must begin with '
87 'alphanumeric character'),
87 'alphanumeric character'),
88 value,
88 value,
89 state
89 state
90 )
90 )
91
91
92 return _ValidUsername
92 return _ValidUsername
93
93
94
94
95 def ValidUsersGroup(edit, old_data):
95 def ValidUsersGroup(edit, old_data):
96
96
97 class _ValidUsersGroup(formencode.validators.FancyValidator):
97 class _ValidUsersGroup(formencode.validators.FancyValidator):
98
98
99 def validate_python(self, value, state):
99 def validate_python(self, value, state):
100 if value in ['default']:
100 if value in ['default']:
101 raise formencode.Invalid(_('Invalid group name'), value, state)
101 raise formencode.Invalid(_('Invalid group name'), value, state)
102 #check if group is unique
102 #check if group is unique
103 old_ugname = None
103 old_ugname = None
104 if edit:
104 if edit:
105 old_ugname = UsersGroup.get(
105 old_ugname = UsersGroup.get(
106 old_data.get('users_group_id')).users_group_name
106 old_data.get('users_group_id')).users_group_name
107
107
108 if old_ugname != value or not edit:
108 if old_ugname != value or not edit:
109 if UsersGroup.get_by_group_name(value, cache=False,
109 if UsersGroup.get_by_group_name(value, cache=False,
110 case_insensitive=True):
110 case_insensitive=True):
111 raise formencode.Invalid(_('This users group '
111 raise formencode.Invalid(_('This users group '
112 'already exists'), value,
112 'already exists'), value,
113 state)
113 state)
114
114
115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
116 raise formencode.Invalid(
116 raise formencode.Invalid(
117 _('RepoGroup name may only contain alphanumeric characters '
117 _('RepoGroup name may only contain alphanumeric characters '
118 'underscores, periods or dashes and must begin with '
118 'underscores, periods or dashes and must begin with '
119 'alphanumeric character'),
119 'alphanumeric character'),
120 value,
120 value,
121 state
121 state
122 )
122 )
123
123
124 return _ValidUsersGroup
124 return _ValidUsersGroup
125
125
126
126
127 def ValidReposGroup(edit, old_data):
127 def ValidReposGroup(edit, old_data):
128 class _ValidReposGroup(formencode.validators.FancyValidator):
128 class _ValidReposGroup(formencode.validators.FancyValidator):
129
129
130 def validate_python(self, value, state):
130 def validate_python(self, value, state):
131 # TODO WRITE VALIDATIONS
131 # TODO WRITE VALIDATIONS
132 group_name = value.get('group_name')
132 group_name = value.get('group_name')
133 group_parent_id = value.get('group_parent_id')
133 group_parent_id = value.get('group_parent_id')
134
134
135 # slugify repo group just in case :)
135 # slugify repo group just in case :)
136 slug = repo_name_slug(group_name)
136 slug = repo_name_slug(group_name)
137
137
138 # check for parent of self
138 # check for parent of self
139 parent_of_self = lambda: (
139 parent_of_self = lambda: (
140 old_data['group_id'] == int(group_parent_id)
140 old_data['group_id'] == int(group_parent_id)
141 if group_parent_id else False
141 if group_parent_id else False
142 )
142 )
143 if edit and parent_of_self():
143 if edit and parent_of_self():
144 e_dict = {
144 e_dict = {
145 'group_parent_id': _('Cannot assign this group as parent')
145 'group_parent_id': _('Cannot assign this group as parent')
146 }
146 }
147 raise formencode.Invalid('', value, state,
147 raise formencode.Invalid('', value, state,
148 error_dict=e_dict)
148 error_dict=e_dict)
149
149
150 old_gname = None
150 old_gname = None
151 if edit:
151 if edit:
152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
153
153
154 if old_gname != group_name or not edit:
154 if old_gname != group_name or not edit:
155
155
156 # check group
156 # check group
157 gr = RepoGroup.query()\
157 gr = RepoGroup.query()\
158 .filter(RepoGroup.group_name == slug)\
158 .filter(RepoGroup.group_name == slug)\
159 .filter(RepoGroup.group_parent_id == group_parent_id)\
159 .filter(RepoGroup.group_parent_id == group_parent_id)\
160 .scalar()
160 .scalar()
161
161
162 if gr:
162 if gr:
163 e_dict = {
163 e_dict = {
164 'group_name': _('This group already exists')
164 'group_name': _('This group already exists')
165 }
165 }
166 raise formencode.Invalid('', value, state,
166 raise formencode.Invalid('', value, state,
167 error_dict=e_dict)
167 error_dict=e_dict)
168
168
169 # check for same repo
169 # check for same repo
170 repo = Repository.query()\
170 repo = Repository.query()\
171 .filter(Repository.repo_name == slug)\
171 .filter(Repository.repo_name == slug)\
172 .scalar()
172 .scalar()
173
173
174 if repo:
174 if repo:
175 e_dict = {
175 e_dict = {
176 'group_name': _('Repository with this name already exists')
176 'group_name': _('Repository with this name already exists')
177 }
177 }
178 raise formencode.Invalid('', value, state,
178 raise formencode.Invalid('', value, state,
179 error_dict=e_dict)
179 error_dict=e_dict)
180
180
181 return _ValidReposGroup
181 return _ValidReposGroup
182
182
183
183
184 class ValidPassword(formencode.validators.FancyValidator):
184 class ValidPassword(formencode.validators.FancyValidator):
185
185
186 def to_python(self, value, state):
186 def to_python(self, value, state):
187
187
188 if not value:
188 if not value:
189 return
189 return
190
190
191 if value.get('password'):
191 if value.get('password'):
192 try:
192 try:
193 value['password'] = get_crypt_password(value['password'])
193 value['password'] = get_crypt_password(value['password'])
194 except UnicodeEncodeError:
194 except UnicodeEncodeError:
195 e_dict = {'password': _('Invalid characters in password')}
195 e_dict = {'password': _('Invalid characters in password')}
196 raise formencode.Invalid('', value, state, error_dict=e_dict)
196 raise formencode.Invalid('', value, state, error_dict=e_dict)
197
197
198 if value.get('password_confirmation'):
198 if value.get('password_confirmation'):
199 try:
199 try:
200 value['password_confirmation'] = \
200 value['password_confirmation'] = \
201 get_crypt_password(value['password_confirmation'])
201 get_crypt_password(value['password_confirmation'])
202 except UnicodeEncodeError:
202 except UnicodeEncodeError:
203 e_dict = {
203 e_dict = {
204 'password_confirmation': _('Invalid characters in password')
204 'password_confirmation': _('Invalid characters in password')
205 }
205 }
206 raise formencode.Invalid('', value, state, error_dict=e_dict)
206 raise formencode.Invalid('', value, state, error_dict=e_dict)
207
207
208 if value.get('new_password'):
208 if value.get('new_password'):
209 try:
209 try:
210 value['new_password'] = \
210 value['new_password'] = \
211 get_crypt_password(value['new_password'])
211 get_crypt_password(value['new_password'])
212 except UnicodeEncodeError:
212 except UnicodeEncodeError:
213 e_dict = {'new_password': _('Invalid characters in password')}
213 e_dict = {'new_password': _('Invalid characters in password')}
214 raise formencode.Invalid('', value, state, error_dict=e_dict)
214 raise formencode.Invalid('', value, state, error_dict=e_dict)
215
215
216 return value
216 return value
217
217
218
218
219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
220
220
221 def validate_python(self, value, state):
221 def validate_python(self, value, state):
222
222
223 pass_val = value.get('password') or value.get('new_password')
223 pass_val = value.get('password') or value.get('new_password')
224 if pass_val != value['password_confirmation']:
224 if pass_val != value['password_confirmation']:
225 e_dict = {'password_confirmation':
225 e_dict = {'password_confirmation':
226 _('Passwords do not match')}
226 _('Passwords do not match')}
227 raise formencode.Invalid('', value, state, error_dict=e_dict)
227 raise formencode.Invalid('', value, state, error_dict=e_dict)
228
228
229
229
230 class ValidAuth(formencode.validators.FancyValidator):
230 class ValidAuth(formencode.validators.FancyValidator):
231 messages = {
231 messages = {
232 'invalid_password':_('invalid password'),
232 'invalid_password':_('invalid password'),
233 'invalid_login':_('invalid user name'),
233 'invalid_login':_('invalid user name'),
234 'disabled_account':_('Your account is disabled')
234 'disabled_account':_('Your account is disabled')
235 }
235 }
236
236
237 # error mapping
237 # error mapping
238 e_dict = {'username': messages['invalid_login'],
238 e_dict = {'username': messages['invalid_login'],
239 'password': messages['invalid_password']}
239 'password': messages['invalid_password']}
240 e_dict_disable = {'username': messages['disabled_account']}
240 e_dict_disable = {'username': messages['disabled_account']}
241
241
242 def validate_python(self, value, state):
242 def validate_python(self, value, state):
243 password = value['password']
243 password = value['password']
244 username = value['username']
244 username = value['username']
245 user = User.get_by_username(username)
245 user = User.get_by_username(username)
246
246
247 if authenticate(username, password):
247 if authenticate(username, password):
248 return value
248 return value
249 else:
249 else:
250 if user and user.active is False:
250 if user and user.active is False:
251 log.warning('user %s is disabled' % username)
251 log.warning('user %s is disabled' % username)
252 raise formencode.Invalid(
252 raise formencode.Invalid(
253 self.message('disabled_account',
253 self.message('disabled_account',
254 state=State_obj),
254 state=State_obj),
255 value, state,
255 value, state,
256 error_dict=self.e_dict_disable
256 error_dict=self.e_dict_disable
257 )
257 )
258 else:
258 else:
259 log.warning('user %s failed to authenticate' % username)
259 log.warning('user %s failed to authenticate' % username)
260 raise formencode.Invalid(
260 raise formencode.Invalid(
261 self.message('invalid_password',
261 self.message('invalid_password',
262 state=State_obj), value, state,
262 state=State_obj), value, state,
263 error_dict=self.e_dict
263 error_dict=self.e_dict
264 )
264 )
265
265
266
266
267 class ValidRepoUser(formencode.validators.FancyValidator):
267 class ValidRepoUser(formencode.validators.FancyValidator):
268
268
269 def to_python(self, value, state):
269 def to_python(self, value, state):
270 try:
270 try:
271 User.query().filter(User.active == True)\
271 User.query().filter(User.active == True)\
272 .filter(User.username == value).one()
272 .filter(User.username == value).one()
273 except Exception:
273 except Exception:
274 raise formencode.Invalid(_('This username is not valid'),
274 raise formencode.Invalid(_('This username is not valid'),
275 value, state)
275 value, state)
276 return value
276 return value
277
277
278
278
279 def ValidRepoName(edit, old_data):
279 def ValidRepoName(edit, old_data):
280 class _ValidRepoName(formencode.validators.FancyValidator):
280 class _ValidRepoName(formencode.validators.FancyValidator):
281 def to_python(self, value, state):
281 def to_python(self, value, state):
282
282
283 repo_name = value.get('repo_name')
283 repo_name = value.get('repo_name')
284
284
285 slug = repo_name_slug(repo_name)
285 slug = repo_name_slug(repo_name)
286 if slug in [ADMIN_PREFIX, '']:
286 if slug in [ADMIN_PREFIX, '']:
287 e_dict = {'repo_name': _('This repository name is disallowed')}
287 e_dict = {'repo_name': _('This repository name is disallowed')}
288 raise formencode.Invalid('', value, state, error_dict=e_dict)
288 raise formencode.Invalid('', value, state, error_dict=e_dict)
289
289
290 if value.get('repo_group'):
290 if value.get('repo_group'):
291 gr = RepoGroup.get(value.get('repo_group'))
291 gr = RepoGroup.get(value.get('repo_group'))
292 group_path = gr.full_path
292 group_path = gr.full_path
293 # value needs to be aware of group name in order to check
293 # value needs to be aware of group name in order to check
294 # db key This is an actual just the name to store in the
294 # db key This is an actual just the name to store in the
295 # database
295 # database
296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
297
297
298 else:
298 else:
299 group_path = ''
299 group_path = ''
300 repo_name_full = repo_name
300 repo_name_full = repo_name
301
301
302 value['repo_name_full'] = repo_name_full
302 value['repo_name_full'] = repo_name_full
303 rename = old_data.get('repo_name') != repo_name_full
303 rename = old_data.get('repo_name') != repo_name_full
304 create = not edit
304 create = not edit
305 if rename or create:
305 if rename or create:
306
306
307 if group_path != '':
307 if group_path != '':
308 if Repository.get_by_repo_name(repo_name_full):
308 if Repository.get_by_repo_name(repo_name_full):
309 e_dict = {
309 e_dict = {
310 'repo_name': _('This repository already exists in '
310 'repo_name': _('This repository already exists in '
311 'a group "%s"') % gr.group_name
311 'a group "%s"') % gr.group_name
312 }
312 }
313 raise formencode.Invalid('', value, state,
313 raise formencode.Invalid('', value, state,
314 error_dict=e_dict)
314 error_dict=e_dict)
315 elif RepoGroup.get_by_group_name(repo_name_full):
315 elif RepoGroup.get_by_group_name(repo_name_full):
316 e_dict = {
316 e_dict = {
317 'repo_name': _('There is a group with this name '
317 'repo_name': _('There is a group with this name '
318 'already "%s"') % repo_name_full
318 'already "%s"') % repo_name_full
319 }
319 }
320 raise formencode.Invalid('', value, state,
320 raise formencode.Invalid('', value, state,
321 error_dict=e_dict)
321 error_dict=e_dict)
322
322
323 elif Repository.get_by_repo_name(repo_name_full):
323 elif Repository.get_by_repo_name(repo_name_full):
324 e_dict = {'repo_name': _('This repository '
324 e_dict = {'repo_name': _('This repository '
325 'already exists')}
325 'already exists')}
326 raise formencode.Invalid('', value, state,
326 raise formencode.Invalid('', value, state,
327 error_dict=e_dict)
327 error_dict=e_dict)
328
328
329 return value
329 return value
330
330
331 return _ValidRepoName
331 return _ValidRepoName
332
332
333
333
334 def ValidForkName(*args, **kwargs):
334 def ValidForkName(*args, **kwargs):
335 return ValidRepoName(*args, **kwargs)
335 return ValidRepoName(*args, **kwargs)
336
336
337
337
338 def SlugifyName():
338 def SlugifyName():
339 class _SlugifyName(formencode.validators.FancyValidator):
339 class _SlugifyName(formencode.validators.FancyValidator):
340
340
341 def to_python(self, value, state):
341 def to_python(self, value, state):
342 return repo_name_slug(value)
342 return repo_name_slug(value)
343
343
344 return _SlugifyName
344 return _SlugifyName
345
345
346
346
347 def ValidCloneUri():
347 def ValidCloneUri():
348 from rhodecode.lib.utils import make_ui
348 from rhodecode.lib.utils import make_ui
349
349
350 def url_handler(repo_type, url, proto, ui=None):
350 def url_handler(repo_type, url, proto, ui=None):
351 if repo_type == 'hg':
351 if repo_type == 'hg':
352 from mercurial.httprepo import httprepository, httpsrepository
352 from mercurial.httprepo import httprepository, httpsrepository
353 if proto == 'https':
353 if proto == 'https':
354 httpsrepository(make_ui('db'), url).capabilities
354 httpsrepository(make_ui('db'), url).capabilities
355 elif proto == 'http':
355 elif proto == 'http':
356 httprepository(make_ui('db'), url).capabilities
356 httprepository(make_ui('db'), url).capabilities
357 elif repo_type == 'git':
357 elif repo_type == 'git':
358 #TODO: write a git url validator
358 #TODO: write a git url validator
359 pass
359 pass
360
360
361 class _ValidCloneUri(formencode.validators.FancyValidator):
361 class _ValidCloneUri(formencode.validators.FancyValidator):
362
362
363 def to_python(self, value, state):
363 def to_python(self, value, state):
364
364
365 repo_type = value.get('repo_type')
365 repo_type = value.get('repo_type')
366 url = value.get('clone_uri')
366 url = value.get('clone_uri')
367 e_dict = {'clone_uri': _('invalid clone url')}
367 e_dict = {'clone_uri': _('invalid clone url')}
368
368
369 if not url:
369 if not url:
370 pass
370 pass
371 elif url.startswith('https'):
371 elif url.startswith('https'):
372 try:
372 try:
373 url_handler(repo_type, url, 'https', make_ui('db'))
373 url_handler(repo_type, url, 'https', make_ui('db'))
374 except Exception:
374 except Exception:
375 log.error(traceback.format_exc())
375 log.error(traceback.format_exc())
376 raise formencode.Invalid('', value, state, error_dict=e_dict)
376 raise formencode.Invalid('', value, state, error_dict=e_dict)
377 elif url.startswith('http'):
377 elif url.startswith('http'):
378 try:
378 try:
379 url_handler(repo_type, url, 'http', make_ui('db'))
379 url_handler(repo_type, url, 'http', make_ui('db'))
380 except Exception:
380 except Exception:
381 log.error(traceback.format_exc())
381 log.error(traceback.format_exc())
382 raise formencode.Invalid('', value, state, error_dict=e_dict)
382 raise formencode.Invalid('', value, state, error_dict=e_dict)
383 else:
383 else:
384 e_dict = {'clone_uri': _('Invalid clone url, provide a '
384 e_dict = {'clone_uri': _('Invalid clone url, provide a '
385 'valid clone http\s url')}
385 'valid clone http\s url')}
386 raise formencode.Invalid('', value, state, error_dict=e_dict)
386 raise formencode.Invalid('', value, state, error_dict=e_dict)
387
387
388 return value
388 return value
389
389
390 return _ValidCloneUri
390 return _ValidCloneUri
391
391
392
392
393 def ValidForkType(old_data):
393 def ValidForkType(old_data):
394 class _ValidForkType(formencode.validators.FancyValidator):
394 class _ValidForkType(formencode.validators.FancyValidator):
395
395
396 def to_python(self, value, state):
396 def to_python(self, value, state):
397 if old_data['repo_type'] != value:
397 if old_data['repo_type'] != value:
398 raise formencode.Invalid(_('Fork have to be the same '
398 raise formencode.Invalid(_('Fork have to be the same '
399 'type as original'), value, state)
399 'type as original'), value, state)
400
400
401 return value
401 return value
402 return _ValidForkType
402 return _ValidForkType
403
403
404
404
405 def ValidPerms(type_='repo'):
405 def ValidPerms(type_='repo'):
406 if type_ == 'group':
406 if type_ == 'group':
407 EMPTY_PERM = 'group.none'
407 EMPTY_PERM = 'group.none'
408 elif type_ == 'repo':
408 elif type_ == 'repo':
409 EMPTY_PERM = 'repository.none'
409 EMPTY_PERM = 'repository.none'
410
410
411 class _ValidPerms(formencode.validators.FancyValidator):
411 class _ValidPerms(formencode.validators.FancyValidator):
412 messages = {
412 messages = {
413 'perm_new_member_name':
413 'perm_new_member_name':
414 _('This username or users group name is not valid')
414 _('This username or users group name is not valid')
415 }
415 }
416
416
417 def to_python(self, value, state):
417 def to_python(self, value, state):
418 perms_update = []
418 perms_update = []
419 perms_new = []
419 perms_new = []
420 # build a list of permission to update and new permission to create
420 # build a list of permission to update and new permission to create
421 for k, v in value.items():
421 for k, v in value.items():
422 # means new added member to permissions
422 # means new added member to permissions
423 if k.startswith('perm_new_member'):
423 if k.startswith('perm_new_member'):
424 new_perm = value.get('perm_new_member', False)
424 new_perm = value.get('perm_new_member', False)
425 new_member = value.get('perm_new_member_name', False)
425 new_member = value.get('perm_new_member_name', False)
426 new_type = value.get('perm_new_member_type')
426 new_type = value.get('perm_new_member_type')
427
427
428 if new_member and new_perm:
428 if new_member and new_perm:
429 if (new_member, new_perm, new_type) not in perms_new:
429 if (new_member, new_perm, new_type) not in perms_new:
430 perms_new.append((new_member, new_perm, new_type))
430 perms_new.append((new_member, new_perm, new_type))
431 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
431 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
432 member = k[7:]
432 member = k[7:]
433 t = {'u': 'user',
433 t = {'u': 'user',
434 'g': 'users_group'
434 'g': 'users_group'
435 }[k[0]]
435 }[k[0]]
436 if member == 'default':
436 if member == 'default':
437 if value.get('private'):
437 if value.get('private'):
438 # set none for default when updating to private repo
438 # set none for default when updating to private repo
439 v = EMPTY_PERM
439 v = EMPTY_PERM
440 perms_update.append((member, v, t))
440 perms_update.append((member, v, t))
441
441
442 value['perms_updates'] = perms_update
442 value['perms_updates'] = perms_update
443 value['perms_new'] = perms_new
443 value['perms_new'] = perms_new
444
444
445 # update permissions
445 # update permissions
446 for k, v, t in perms_new:
446 for k, v, t in perms_new:
447 try:
447 try:
448 if t is 'user':
448 if t is 'user':
449 self.user_db = User.query()\
449 self.user_db = User.query()\
450 .filter(User.active == True)\
450 .filter(User.active == True)\
451 .filter(User.username == k).one()
451 .filter(User.username == k).one()
452 if t is 'users_group':
452 if t is 'users_group':
453 self.user_db = UsersGroup.query()\
453 self.user_db = UsersGroup.query()\
454 .filter(UsersGroup.users_group_active == True)\
454 .filter(UsersGroup.users_group_active == True)\
455 .filter(UsersGroup.users_group_name == k).one()
455 .filter(UsersGroup.users_group_name == k).one()
456
456
457 except Exception:
457 except Exception:
458 msg = self.message('perm_new_member_name',
458 msg = self.message('perm_new_member_name',
459 state=State_obj)
459 state=State_obj)
460 raise formencode.Invalid(
460 raise formencode.Invalid(
461 msg, value, state, error_dict={'perm_new_member_name': msg}
461 msg, value, state, error_dict={'perm_new_member_name': msg}
462 )
462 )
463 return value
463 return value
464 return _ValidPerms
464 return _ValidPerms
465
465
466
466
467 class ValidSettings(formencode.validators.FancyValidator):
467 class ValidSettings(formencode.validators.FancyValidator):
468
468
469 def to_python(self, value, state):
469 def to_python(self, value, state):
470 # settings form can't edit user
470 # settings form can't edit user
471 if 'user' in value:
471 if 'user' in value:
472 del['value']['user']
472 del['value']['user']
473 return value
473 return value
474
474
475
475
476 class ValidPath(formencode.validators.FancyValidator):
476 class ValidPath(formencode.validators.FancyValidator):
477 def to_python(self, value, state):
477 def to_python(self, value, state):
478
478
479 if not os.path.isdir(value):
479 if not os.path.isdir(value):
480 msg = _('This is not a valid path')
480 msg = _('This is not a valid path')
481 raise formencode.Invalid(msg, value, state,
481 raise formencode.Invalid(msg, value, state,
482 error_dict={'paths_root_path': msg})
482 error_dict={'paths_root_path': msg})
483 return value
483 return value
484
484
485
485
486 def UniqSystemEmail(old_data):
486 def UniqSystemEmail(old_data):
487 class _UniqSystemEmail(formencode.validators.FancyValidator):
487 class _UniqSystemEmail(formencode.validators.FancyValidator):
488 def to_python(self, value, state):
488 def to_python(self, value, state):
489 value = value.lower()
489 value = value.lower()
490 if old_data.get('email', '').lower() != value:
490 if (old_data.get('email') or '').lower() != value:
491 user = User.get_by_email(value, case_insensitive=True)
491 user = User.get_by_email(value, case_insensitive=True)
492 if user:
492 if user:
493 raise formencode.Invalid(
493 raise formencode.Invalid(
494 _("This e-mail address is already taken"), value, state
494 _("This e-mail address is already taken"), value, state
495 )
495 )
496 return value
496 return value
497
497
498 return _UniqSystemEmail
498 return _UniqSystemEmail
499
499
500
500
501 class ValidSystemEmail(formencode.validators.FancyValidator):
501 class ValidSystemEmail(formencode.validators.FancyValidator):
502 def to_python(self, value, state):
502 def to_python(self, value, state):
503 value = value.lower()
503 value = value.lower()
504 user = User.get_by_email(value, case_insensitive=True)
504 user = User.get_by_email(value, case_insensitive=True)
505 if user is None:
505 if user is None:
506 raise formencode.Invalid(
506 raise formencode.Invalid(
507 _("This e-mail address doesn't exist."), value, state
507 _("This e-mail address doesn't exist."), value, state
508 )
508 )
509
509
510 return value
510 return value
511
511
512
512
513 class LdapLibValidator(formencode.validators.FancyValidator):
513 class LdapLibValidator(formencode.validators.FancyValidator):
514
514
515 def to_python(self, value, state):
515 def to_python(self, value, state):
516
516
517 try:
517 try:
518 import ldap
518 import ldap
519 except ImportError:
519 except ImportError:
520 raise LdapImportError
520 raise LdapImportError
521 return value
521 return value
522
522
523
523
524 class AttrLoginValidator(formencode.validators.FancyValidator):
524 class AttrLoginValidator(formencode.validators.FancyValidator):
525
525
526 def to_python(self, value, state):
526 def to_python(self, value, state):
527
527
528 if not value or not isinstance(value, (str, unicode)):
528 if not value or not isinstance(value, (str, unicode)):
529 raise formencode.Invalid(
529 raise formencode.Invalid(
530 _("The LDAP Login attribute of the CN must be specified - "
530 _("The LDAP Login attribute of the CN must be specified - "
531 "this is the name of the attribute that is equivalent "
531 "this is the name of the attribute that is equivalent "
532 "to 'username'"), value, state
532 "to 'username'"), value, state
533 )
533 )
534
534
535 return value
535 return value
536
536
537
537
538 #==============================================================================
538 #==============================================================================
539 # FORMS
539 # FORMS
540 #==============================================================================
540 #==============================================================================
541 class LoginForm(formencode.Schema):
541 class LoginForm(formencode.Schema):
542 allow_extra_fields = True
542 allow_extra_fields = True
543 filter_extra_fields = True
543 filter_extra_fields = True
544 username = UnicodeString(
544 username = UnicodeString(
545 strip=True,
545 strip=True,
546 min=1,
546 min=1,
547 not_empty=True,
547 not_empty=True,
548 messages={
548 messages={
549 'empty': _('Please enter a login'),
549 'empty': _('Please enter a login'),
550 'tooShort': _('Enter a value %(min)i characters long or more')}
550 'tooShort': _('Enter a value %(min)i characters long or more')}
551 )
551 )
552
552
553 password = UnicodeString(
553 password = UnicodeString(
554 strip=True,
554 strip=True,
555 min=3,
555 min=3,
556 not_empty=True,
556 not_empty=True,
557 messages={
557 messages={
558 'empty': _('Please enter a password'),
558 'empty': _('Please enter a password'),
559 'tooShort': _('Enter %(min)i characters or more')}
559 'tooShort': _('Enter %(min)i characters or more')}
560 )
560 )
561
561
562 remember = StringBoolean(if_missing=False)
562 remember = StringBoolean(if_missing=False)
563
563
564 chained_validators = [ValidAuth]
564 chained_validators = [ValidAuth]
565
565
566
566
567 def UserForm(edit=False, old_data={}):
567 def UserForm(edit=False, old_data={}):
568 class _UserForm(formencode.Schema):
568 class _UserForm(formencode.Schema):
569 allow_extra_fields = True
569 allow_extra_fields = True
570 filter_extra_fields = True
570 filter_extra_fields = True
571 username = All(UnicodeString(strip=True, min=1, not_empty=True),
571 username = All(UnicodeString(strip=True, min=1, not_empty=True),
572 ValidUsername(edit, old_data))
572 ValidUsername(edit, old_data))
573 if edit:
573 if edit:
574 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
574 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
575 password_confirmation = All(UnicodeString(strip=True, min=6,
575 password_confirmation = All(UnicodeString(strip=True, min=6,
576 not_empty=False))
576 not_empty=False))
577 admin = StringBoolean(if_missing=False)
577 admin = StringBoolean(if_missing=False)
578 else:
578 else:
579 password = All(UnicodeString(strip=True, min=6, not_empty=True))
579 password = All(UnicodeString(strip=True, min=6, not_empty=True))
580 password_confirmation = All(UnicodeString(strip=True, min=6,
580 password_confirmation = All(UnicodeString(strip=True, min=6,
581 not_empty=False))
581 not_empty=False))
582
582
583 active = StringBoolean(if_missing=False)
583 active = StringBoolean(if_missing=False)
584 name = UnicodeString(strip=True, min=1, not_empty=False)
584 name = UnicodeString(strip=True, min=1, not_empty=False)
585 lastname = UnicodeString(strip=True, min=1, not_empty=False)
585 lastname = UnicodeString(strip=True, min=1, not_empty=False)
586 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
586 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
587
587
588 chained_validators = [ValidPasswordsMatch, ValidPassword]
588 chained_validators = [ValidPasswordsMatch, ValidPassword]
589
589
590 return _UserForm
590 return _UserForm
591
591
592
592
593 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
593 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
594 class _UsersGroupForm(formencode.Schema):
594 class _UsersGroupForm(formencode.Schema):
595 allow_extra_fields = True
595 allow_extra_fields = True
596 filter_extra_fields = True
596 filter_extra_fields = True
597
597
598 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
598 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
599 ValidUsersGroup(edit, old_data))
599 ValidUsersGroup(edit, old_data))
600
600
601 users_group_active = StringBoolean(if_missing=False)
601 users_group_active = StringBoolean(if_missing=False)
602
602
603 if edit:
603 if edit:
604 users_group_members = OneOf(available_members, hideList=False,
604 users_group_members = OneOf(available_members, hideList=False,
605 testValueList=True,
605 testValueList=True,
606 if_missing=None, not_empty=False)
606 if_missing=None, not_empty=False)
607
607
608 return _UsersGroupForm
608 return _UsersGroupForm
609
609
610
610
611 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
611 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
612 class _ReposGroupForm(formencode.Schema):
612 class _ReposGroupForm(formencode.Schema):
613 allow_extra_fields = True
613 allow_extra_fields = True
614 filter_extra_fields = False
614 filter_extra_fields = False
615
615
616 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
616 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
617 SlugifyName())
617 SlugifyName())
618 group_description = UnicodeString(strip=True, min=1,
618 group_description = UnicodeString(strip=True, min=1,
619 not_empty=True)
619 not_empty=True)
620 group_parent_id = OneOf(available_groups, hideList=False,
620 group_parent_id = OneOf(available_groups, hideList=False,
621 testValueList=True,
621 testValueList=True,
622 if_missing=None, not_empty=False)
622 if_missing=None, not_empty=False)
623
623
624 chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
624 chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
625
625
626 return _ReposGroupForm
626 return _ReposGroupForm
627
627
628
628
629 def RegisterForm(edit=False, old_data={}):
629 def RegisterForm(edit=False, old_data={}):
630 class _RegisterForm(formencode.Schema):
630 class _RegisterForm(formencode.Schema):
631 allow_extra_fields = True
631 allow_extra_fields = True
632 filter_extra_fields = True
632 filter_extra_fields = True
633 username = All(ValidUsername(edit, old_data),
633 username = All(ValidUsername(edit, old_data),
634 UnicodeString(strip=True, min=1, not_empty=True))
634 UnicodeString(strip=True, min=1, not_empty=True))
635 password = All(UnicodeString(strip=True, min=6, not_empty=True))
635 password = All(UnicodeString(strip=True, min=6, not_empty=True))
636 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
636 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
637 active = StringBoolean(if_missing=False)
637 active = StringBoolean(if_missing=False)
638 name = UnicodeString(strip=True, min=1, not_empty=False)
638 name = UnicodeString(strip=True, min=1, not_empty=False)
639 lastname = UnicodeString(strip=True, min=1, not_empty=False)
639 lastname = UnicodeString(strip=True, min=1, not_empty=False)
640 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
640 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
641
641
642 chained_validators = [ValidPasswordsMatch, ValidPassword]
642 chained_validators = [ValidPasswordsMatch, ValidPassword]
643
643
644 return _RegisterForm
644 return _RegisterForm
645
645
646
646
647 def PasswordResetForm():
647 def PasswordResetForm():
648 class _PasswordResetForm(formencode.Schema):
648 class _PasswordResetForm(formencode.Schema):
649 allow_extra_fields = True
649 allow_extra_fields = True
650 filter_extra_fields = True
650 filter_extra_fields = True
651 email = All(ValidSystemEmail(), Email(not_empty=True))
651 email = All(ValidSystemEmail(), Email(not_empty=True))
652 return _PasswordResetForm
652 return _PasswordResetForm
653
653
654
654
655 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
655 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
656 repo_groups=[]):
656 repo_groups=[]):
657 class _RepoForm(formencode.Schema):
657 class _RepoForm(formencode.Schema):
658 allow_extra_fields = True
658 allow_extra_fields = True
659 filter_extra_fields = False
659 filter_extra_fields = False
660 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
660 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
661 SlugifyName())
661 SlugifyName())
662 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False))
662 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False))
663 repo_group = OneOf(repo_groups, hideList=True)
663 repo_group = OneOf(repo_groups, hideList=True)
664 repo_type = OneOf(supported_backends)
664 repo_type = OneOf(supported_backends)
665 description = UnicodeString(strip=True, min=1, not_empty=True)
665 description = UnicodeString(strip=True, min=1, not_empty=True)
666 private = StringBoolean(if_missing=False)
666 private = StringBoolean(if_missing=False)
667 enable_statistics = StringBoolean(if_missing=False)
667 enable_statistics = StringBoolean(if_missing=False)
668 enable_downloads = StringBoolean(if_missing=False)
668 enable_downloads = StringBoolean(if_missing=False)
669
669
670 if edit:
670 if edit:
671 #this is repo owner
671 #this is repo owner
672 user = All(UnicodeString(not_empty=True), ValidRepoUser)
672 user = All(UnicodeString(not_empty=True), ValidRepoUser)
673
673
674 chained_validators = [ValidCloneUri()(),
674 chained_validators = [ValidCloneUri()(),
675 ValidRepoName(edit, old_data),
675 ValidRepoName(edit, old_data),
676 ValidPerms()]
676 ValidPerms()]
677 return _RepoForm
677 return _RepoForm
678
678
679
679
680 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
680 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
681 repo_groups=[]):
681 repo_groups=[]):
682 class _RepoForkForm(formencode.Schema):
682 class _RepoForkForm(formencode.Schema):
683 allow_extra_fields = True
683 allow_extra_fields = True
684 filter_extra_fields = False
684 filter_extra_fields = False
685 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
685 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
686 SlugifyName())
686 SlugifyName())
687 repo_group = OneOf(repo_groups, hideList=True)
687 repo_group = OneOf(repo_groups, hideList=True)
688 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
688 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
689 description = UnicodeString(strip=True, min=1, not_empty=True)
689 description = UnicodeString(strip=True, min=1, not_empty=True)
690 private = StringBoolean(if_missing=False)
690 private = StringBoolean(if_missing=False)
691 copy_permissions = StringBoolean(if_missing=False)
691 copy_permissions = StringBoolean(if_missing=False)
692 update_after_clone = StringBoolean(if_missing=False)
692 update_after_clone = StringBoolean(if_missing=False)
693 fork_parent_id = UnicodeString()
693 fork_parent_id = UnicodeString()
694 chained_validators = [ValidForkName(edit, old_data)]
694 chained_validators = [ValidForkName(edit, old_data)]
695
695
696 return _RepoForkForm
696 return _RepoForkForm
697
697
698
698
699 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
699 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
700 repo_groups=[]):
700 repo_groups=[]):
701 class _RepoForm(formencode.Schema):
701 class _RepoForm(formencode.Schema):
702 allow_extra_fields = True
702 allow_extra_fields = True
703 filter_extra_fields = False
703 filter_extra_fields = False
704 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
704 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
705 SlugifyName())
705 SlugifyName())
706 description = UnicodeString(strip=True, min=1, not_empty=True)
706 description = UnicodeString(strip=True, min=1, not_empty=True)
707 repo_group = OneOf(repo_groups, hideList=True)
707 repo_group = OneOf(repo_groups, hideList=True)
708 private = StringBoolean(if_missing=False)
708 private = StringBoolean(if_missing=False)
709
709
710 chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
710 chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
711 ValidSettings]
711 ValidSettings]
712 return _RepoForm
712 return _RepoForm
713
713
714
714
715 def ApplicationSettingsForm():
715 def ApplicationSettingsForm():
716 class _ApplicationSettingsForm(formencode.Schema):
716 class _ApplicationSettingsForm(formencode.Schema):
717 allow_extra_fields = True
717 allow_extra_fields = True
718 filter_extra_fields = False
718 filter_extra_fields = False
719 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
719 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
720 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
720 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
721 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
721 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
722
722
723 return _ApplicationSettingsForm
723 return _ApplicationSettingsForm
724
724
725
725
726 def ApplicationUiSettingsForm():
726 def ApplicationUiSettingsForm():
727 class _ApplicationUiSettingsForm(formencode.Schema):
727 class _ApplicationUiSettingsForm(formencode.Schema):
728 allow_extra_fields = True
728 allow_extra_fields = True
729 filter_extra_fields = False
729 filter_extra_fields = False
730 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
730 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
731 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
731 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
732 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
732 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
733 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
733 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
734 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
734 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
735 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
735 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
736
736
737 return _ApplicationUiSettingsForm
737 return _ApplicationUiSettingsForm
738
738
739
739
740 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
740 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
741 class _DefaultPermissionsForm(formencode.Schema):
741 class _DefaultPermissionsForm(formencode.Schema):
742 allow_extra_fields = True
742 allow_extra_fields = True
743 filter_extra_fields = True
743 filter_extra_fields = True
744 overwrite_default = StringBoolean(if_missing=False)
744 overwrite_default = StringBoolean(if_missing=False)
745 anonymous = OneOf(['True', 'False'], if_missing=False)
745 anonymous = OneOf(['True', 'False'], if_missing=False)
746 default_perm = OneOf(perms_choices)
746 default_perm = OneOf(perms_choices)
747 default_register = OneOf(register_choices)
747 default_register = OneOf(register_choices)
748 default_create = OneOf(create_choices)
748 default_create = OneOf(create_choices)
749
749
750 return _DefaultPermissionsForm
750 return _DefaultPermissionsForm
751
751
752
752
753 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
753 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
754 class _LdapSettingsForm(formencode.Schema):
754 class _LdapSettingsForm(formencode.Schema):
755 allow_extra_fields = True
755 allow_extra_fields = True
756 filter_extra_fields = True
756 filter_extra_fields = True
757 pre_validators = [LdapLibValidator]
757 pre_validators = [LdapLibValidator]
758 ldap_active = StringBoolean(if_missing=False)
758 ldap_active = StringBoolean(if_missing=False)
759 ldap_host = UnicodeString(strip=True,)
759 ldap_host = UnicodeString(strip=True,)
760 ldap_port = Number(strip=True,)
760 ldap_port = Number(strip=True,)
761 ldap_tls_kind = OneOf(tls_kind_choices)
761 ldap_tls_kind = OneOf(tls_kind_choices)
762 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
762 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
763 ldap_dn_user = UnicodeString(strip=True,)
763 ldap_dn_user = UnicodeString(strip=True,)
764 ldap_dn_pass = UnicodeString(strip=True,)
764 ldap_dn_pass = UnicodeString(strip=True,)
765 ldap_base_dn = UnicodeString(strip=True,)
765 ldap_base_dn = UnicodeString(strip=True,)
766 ldap_filter = UnicodeString(strip=True,)
766 ldap_filter = UnicodeString(strip=True,)
767 ldap_search_scope = OneOf(search_scope_choices)
767 ldap_search_scope = OneOf(search_scope_choices)
768 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
768 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
769 ldap_attr_firstname = UnicodeString(strip=True,)
769 ldap_attr_firstname = UnicodeString(strip=True,)
770 ldap_attr_lastname = UnicodeString(strip=True,)
770 ldap_attr_lastname = UnicodeString(strip=True,)
771 ldap_attr_email = UnicodeString(strip=True,)
771 ldap_attr_email = UnicodeString(strip=True,)
772
772
773 return _LdapSettingsForm
773 return _LdapSettingsForm
@@ -1,456 +1,459 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or 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 time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29 import cStringIO
29 import cStringIO
30
30
31 from rhodecode.lib.vcs import get_backend
31 from rhodecode.lib.vcs import get_backend
32 from rhodecode.lib.vcs.exceptions import RepositoryError
32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 from rhodecode.lib.vcs.utils.lazy import LazyProperty
33 from rhodecode.lib.vcs.utils.lazy import LazyProperty
34 from rhodecode.lib.vcs.nodes import FileNode
34 from rhodecode.lib.vcs.nodes import FileNode
35
35
36 from rhodecode import BACKENDS
36 from rhodecode import BACKENDS
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import safe_str
38 from rhodecode.lib import safe_str
39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 action_logger, EmptyChangeset
41 action_logger, EmptyChangeset, REMOVED_REPO_PAT
42 from rhodecode.model import BaseModel
42 from rhodecode.model import BaseModel
43 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
43 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
44 UserFollowing, UserLog, User, RepoGroup
44 UserFollowing, UserLog, User, RepoGroup
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class UserTemp(object):
49 class UserTemp(object):
50 def __init__(self, user_id):
50 def __init__(self, user_id):
51 self.user_id = user_id
51 self.user_id = user_id
52
52
53 def __repr__(self):
53 def __repr__(self):
54 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
54 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
55
55
56
56
57 class RepoTemp(object):
57 class RepoTemp(object):
58 def __init__(self, repo_id):
58 def __init__(self, repo_id):
59 self.repo_id = repo_id
59 self.repo_id = repo_id
60
60
61 def __repr__(self):
61 def __repr__(self):
62 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
62 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
63
63
64
64
65 class CachedRepoList(object):
65 class CachedRepoList(object):
66
66
67 def __init__(self, db_repo_list, repos_path, order_by=None):
67 def __init__(self, db_repo_list, repos_path, order_by=None):
68 self.db_repo_list = db_repo_list
68 self.db_repo_list = db_repo_list
69 self.repos_path = repos_path
69 self.repos_path = repos_path
70 self.order_by = order_by
70 self.order_by = order_by
71 self.reversed = (order_by or '').startswith('-')
71 self.reversed = (order_by or '').startswith('-')
72
72
73 def __len__(self):
73 def __len__(self):
74 return len(self.db_repo_list)
74 return len(self.db_repo_list)
75
75
76 def __repr__(self):
76 def __repr__(self):
77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
78
78
79 def __iter__(self):
79 def __iter__(self):
80 for dbr in self.db_repo_list:
80 for dbr in self.db_repo_list:
81 scmr = dbr.scm_instance_cached
81 scmr = dbr.scm_instance_cached
82 # check permission at this level
82 # check permission at this level
83 if not HasRepoPermissionAny(
83 if not HasRepoPermissionAny(
84 'repository.read', 'repository.write', 'repository.admin'
84 'repository.read', 'repository.write', 'repository.admin'
85 )(dbr.repo_name, 'get repo check'):
85 )(dbr.repo_name, 'get repo check'):
86 continue
86 continue
87
87
88 if scmr is None:
88 if scmr is None:
89 log.error(
89 log.error(
90 '%s this repository is present in database but it '
90 '%s this repository is present in database but it '
91 'cannot be created as an scm instance' % dbr.repo_name
91 'cannot be created as an scm instance' % dbr.repo_name
92 )
92 )
93 continue
93 continue
94
94
95 last_change = scmr.last_change
95 last_change = scmr.last_change
96 tip = h.get_changeset_safe(scmr, 'tip')
96 tip = h.get_changeset_safe(scmr, 'tip')
97
97
98 tmp_d = {}
98 tmp_d = {}
99 tmp_d['name'] = dbr.repo_name
99 tmp_d['name'] = dbr.repo_name
100 tmp_d['name_sort'] = tmp_d['name'].lower()
100 tmp_d['name_sort'] = tmp_d['name'].lower()
101 tmp_d['description'] = dbr.description
101 tmp_d['description'] = dbr.description
102 tmp_d['description_sort'] = tmp_d['description']
102 tmp_d['description_sort'] = tmp_d['description']
103 tmp_d['last_change'] = last_change
103 tmp_d['last_change'] = last_change
104 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
104 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
105 tmp_d['tip'] = tip.raw_id
105 tmp_d['tip'] = tip.raw_id
106 tmp_d['tip_sort'] = tip.revision
106 tmp_d['tip_sort'] = tip.revision
107 tmp_d['rev'] = tip.revision
107 tmp_d['rev'] = tip.revision
108 tmp_d['contact'] = dbr.user.full_contact
108 tmp_d['contact'] = dbr.user.full_contact
109 tmp_d['contact_sort'] = tmp_d['contact']
109 tmp_d['contact_sort'] = tmp_d['contact']
110 tmp_d['owner_sort'] = tmp_d['contact']
110 tmp_d['owner_sort'] = tmp_d['contact']
111 tmp_d['repo_archives'] = list(scmr._get_archives())
111 tmp_d['repo_archives'] = list(scmr._get_archives())
112 tmp_d['last_msg'] = tip.message
112 tmp_d['last_msg'] = tip.message
113 tmp_d['author'] = tip.author
113 tmp_d['author'] = tip.author
114 tmp_d['dbrepo'] = dbr.get_dict()
114 tmp_d['dbrepo'] = dbr.get_dict()
115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
116 yield tmp_d
116 yield tmp_d
117
117
118
118
119 class GroupList(object):
119 class GroupList(object):
120
120
121 def __init__(self, db_repo_group_list):
121 def __init__(self, db_repo_group_list):
122 self.db_repo_group_list = db_repo_group_list
122 self.db_repo_group_list = db_repo_group_list
123
123
124 def __len__(self):
124 def __len__(self):
125 return len(self.db_repo_group_list)
125 return len(self.db_repo_group_list)
126
126
127 def __repr__(self):
127 def __repr__(self):
128 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
128 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
129
129
130 def __iter__(self):
130 def __iter__(self):
131 for dbgr in self.db_repo_group_list:
131 for dbgr in self.db_repo_group_list:
132 # check permission at this level
132 # check permission at this level
133 if not HasReposGroupPermissionAny(
133 if not HasReposGroupPermissionAny(
134 'group.read', 'group.write', 'group.admin'
134 'group.read', 'group.write', 'group.admin'
135 )(dbgr.group_name, 'get group repo check'):
135 )(dbgr.group_name, 'get group repo check'):
136 continue
136 continue
137
137
138 yield dbgr
138 yield dbgr
139
139
140
140
141 class ScmModel(BaseModel):
141 class ScmModel(BaseModel):
142 """
142 """
143 Generic Scm Model
143 Generic Scm Model
144 """
144 """
145
145
146 def __get_repo(self, instance):
146 def __get_repo(self, instance):
147 cls = Repository
147 cls = Repository
148 if isinstance(instance, cls):
148 if isinstance(instance, cls):
149 return instance
149 return instance
150 elif isinstance(instance, int) or str(instance).isdigit():
150 elif isinstance(instance, int) or str(instance).isdigit():
151 return cls.get(instance)
151 return cls.get(instance)
152 elif isinstance(instance, basestring):
152 elif isinstance(instance, basestring):
153 return cls.get_by_repo_name(instance)
153 return cls.get_by_repo_name(instance)
154 elif instance:
154 elif instance:
155 raise Exception('given object must be int, basestr or Instance'
155 raise Exception('given object must be int, basestr or Instance'
156 ' of %s got %s' % (type(cls), type(instance)))
156 ' of %s got %s' % (type(cls), type(instance)))
157
157
158 @LazyProperty
158 @LazyProperty
159 def repos_path(self):
159 def repos_path(self):
160 """
160 """
161 Get's the repositories root path from database
161 Get's the repositories root path from database
162 """
162 """
163
163
164 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
164 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
165
165
166 return q.ui_value
166 return q.ui_value
167
167
168 def repo_scan(self, repos_path=None):
168 def repo_scan(self, repos_path=None):
169 """
169 """
170 Listing of repositories in given path. This path should not be a
170 Listing of repositories in given path. This path should not be a
171 repository itself. Return a dictionary of repository objects
171 repository itself. Return a dictionary of repository objects
172
172
173 :param repos_path: path to directory containing repositories
173 :param repos_path: path to directory containing repositories
174 """
174 """
175
175
176 if repos_path is None:
176 if repos_path is None:
177 repos_path = self.repos_path
177 repos_path = self.repos_path
178
178
179 log.info('scanning for repositories in %s' % repos_path)
179 log.info('scanning for repositories in %s' % repos_path)
180
180
181 baseui = make_ui('db')
181 baseui = make_ui('db')
182 repos = {}
182 repos = {}
183
183
184 for name, path in get_filesystem_repos(repos_path, recursive=True):
184 for name, path in get_filesystem_repos(repos_path, recursive=True):
185 # skip removed repos
186 if REMOVED_REPO_PAT.match(name):
187 continue
185
188
186 # name need to be decomposed and put back together using the /
189 # name need to be decomposed and put back together using the /
187 # since this is internal storage separator for rhodecode
190 # since this is internal storage separator for rhodecode
188 name = Repository.url_sep().join(name.split(os.sep))
191 name = Repository.url_sep().join(name.split(os.sep))
189
192
190 try:
193 try:
191 if name in repos:
194 if name in repos:
192 raise RepositoryError('Duplicate repository name %s '
195 raise RepositoryError('Duplicate repository name %s '
193 'found in %s' % (name, path))
196 'found in %s' % (name, path))
194 else:
197 else:
195
198
196 klass = get_backend(path[0])
199 klass = get_backend(path[0])
197
200
198 if path[0] == 'hg' and path[0] in BACKENDS.keys():
201 if path[0] == 'hg' and path[0] in BACKENDS.keys():
199 repos[name] = klass(safe_str(path[1]), baseui=baseui)
202 repos[name] = klass(safe_str(path[1]), baseui=baseui)
200
203
201 if path[0] == 'git' and path[0] in BACKENDS.keys():
204 if path[0] == 'git' and path[0] in BACKENDS.keys():
202 repos[name] = klass(path[1])
205 repos[name] = klass(path[1])
203 except OSError:
206 except OSError:
204 continue
207 continue
205
208
206 return repos
209 return repos
207
210
208 def get_repos(self, all_repos=None, sort_key=None):
211 def get_repos(self, all_repos=None, sort_key=None):
209 """
212 """
210 Get all repos from db and for each repo create it's
213 Get all repos from db and for each repo create it's
211 backend instance and fill that backed with information from database
214 backend instance and fill that backed with information from database
212
215
213 :param all_repos: list of repository names as strings
216 :param all_repos: list of repository names as strings
214 give specific repositories list, good for filtering
217 give specific repositories list, good for filtering
215 """
218 """
216 if all_repos is None:
219 if all_repos is None:
217 all_repos = self.sa.query(Repository)\
220 all_repos = self.sa.query(Repository)\
218 .filter(Repository.group_id == None)\
221 .filter(Repository.group_id == None)\
219 .order_by(Repository.repo_name).all()
222 .order_by(Repository.repo_name).all()
220
223
221 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
224 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
222 order_by=sort_key)
225 order_by=sort_key)
223
226
224 return repo_iter
227 return repo_iter
225
228
226 def get_repos_groups(self, all_groups=None):
229 def get_repos_groups(self, all_groups=None):
227 if all_groups is None:
230 if all_groups is None:
228 all_groups = RepoGroup.query()\
231 all_groups = RepoGroup.query()\
229 .filter(RepoGroup.group_parent_id == None).all()
232 .filter(RepoGroup.group_parent_id == None).all()
230 group_iter = GroupList(all_groups)
233 group_iter = GroupList(all_groups)
231
234
232 return group_iter
235 return group_iter
233
236
234 def mark_for_invalidation(self, repo_name):
237 def mark_for_invalidation(self, repo_name):
235 """Puts cache invalidation task into db for
238 """Puts cache invalidation task into db for
236 further global cache invalidation
239 further global cache invalidation
237
240
238 :param repo_name: this repo that should invalidation take place
241 :param repo_name: this repo that should invalidation take place
239 """
242 """
240 CacheInvalidation.set_invalidate(repo_name)
243 CacheInvalidation.set_invalidate(repo_name)
241 CacheInvalidation.set_invalidate(repo_name + "_README")
244 CacheInvalidation.set_invalidate(repo_name + "_README")
242
245
243 def toggle_following_repo(self, follow_repo_id, user_id):
246 def toggle_following_repo(self, follow_repo_id, user_id):
244
247
245 f = self.sa.query(UserFollowing)\
248 f = self.sa.query(UserFollowing)\
246 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
249 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
247 .filter(UserFollowing.user_id == user_id).scalar()
250 .filter(UserFollowing.user_id == user_id).scalar()
248
251
249 if f is not None:
252 if f is not None:
250 try:
253 try:
251 self.sa.delete(f)
254 self.sa.delete(f)
252 action_logger(UserTemp(user_id),
255 action_logger(UserTemp(user_id),
253 'stopped_following_repo',
256 'stopped_following_repo',
254 RepoTemp(follow_repo_id))
257 RepoTemp(follow_repo_id))
255 return
258 return
256 except:
259 except:
257 log.error(traceback.format_exc())
260 log.error(traceback.format_exc())
258 raise
261 raise
259
262
260 try:
263 try:
261 f = UserFollowing()
264 f = UserFollowing()
262 f.user_id = user_id
265 f.user_id = user_id
263 f.follows_repo_id = follow_repo_id
266 f.follows_repo_id = follow_repo_id
264 self.sa.add(f)
267 self.sa.add(f)
265
268
266 action_logger(UserTemp(user_id),
269 action_logger(UserTemp(user_id),
267 'started_following_repo',
270 'started_following_repo',
268 RepoTemp(follow_repo_id))
271 RepoTemp(follow_repo_id))
269 except:
272 except:
270 log.error(traceback.format_exc())
273 log.error(traceback.format_exc())
271 raise
274 raise
272
275
273 def toggle_following_user(self, follow_user_id, user_id):
276 def toggle_following_user(self, follow_user_id, user_id):
274 f = self.sa.query(UserFollowing)\
277 f = self.sa.query(UserFollowing)\
275 .filter(UserFollowing.follows_user_id == follow_user_id)\
278 .filter(UserFollowing.follows_user_id == follow_user_id)\
276 .filter(UserFollowing.user_id == user_id).scalar()
279 .filter(UserFollowing.user_id == user_id).scalar()
277
280
278 if f is not None:
281 if f is not None:
279 try:
282 try:
280 self.sa.delete(f)
283 self.sa.delete(f)
281 return
284 return
282 except:
285 except:
283 log.error(traceback.format_exc())
286 log.error(traceback.format_exc())
284 raise
287 raise
285
288
286 try:
289 try:
287 f = UserFollowing()
290 f = UserFollowing()
288 f.user_id = user_id
291 f.user_id = user_id
289 f.follows_user_id = follow_user_id
292 f.follows_user_id = follow_user_id
290 self.sa.add(f)
293 self.sa.add(f)
291 except:
294 except:
292 log.error(traceback.format_exc())
295 log.error(traceback.format_exc())
293 raise
296 raise
294
297
295 def is_following_repo(self, repo_name, user_id, cache=False):
298 def is_following_repo(self, repo_name, user_id, cache=False):
296 r = self.sa.query(Repository)\
299 r = self.sa.query(Repository)\
297 .filter(Repository.repo_name == repo_name).scalar()
300 .filter(Repository.repo_name == repo_name).scalar()
298
301
299 f = self.sa.query(UserFollowing)\
302 f = self.sa.query(UserFollowing)\
300 .filter(UserFollowing.follows_repository == r)\
303 .filter(UserFollowing.follows_repository == r)\
301 .filter(UserFollowing.user_id == user_id).scalar()
304 .filter(UserFollowing.user_id == user_id).scalar()
302
305
303 return f is not None
306 return f is not None
304
307
305 def is_following_user(self, username, user_id, cache=False):
308 def is_following_user(self, username, user_id, cache=False):
306 u = User.get_by_username(username)
309 u = User.get_by_username(username)
307
310
308 f = self.sa.query(UserFollowing)\
311 f = self.sa.query(UserFollowing)\
309 .filter(UserFollowing.follows_user == u)\
312 .filter(UserFollowing.follows_user == u)\
310 .filter(UserFollowing.user_id == user_id).scalar()
313 .filter(UserFollowing.user_id == user_id).scalar()
311
314
312 return f is not None
315 return f is not None
313
316
314 def get_followers(self, repo_id):
317 def get_followers(self, repo_id):
315 if not isinstance(repo_id, int):
318 if not isinstance(repo_id, int):
316 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
319 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
317
320
318 return self.sa.query(UserFollowing)\
321 return self.sa.query(UserFollowing)\
319 .filter(UserFollowing.follows_repo_id == repo_id).count()
322 .filter(UserFollowing.follows_repo_id == repo_id).count()
320
323
321 def get_forks(self, repo_id):
324 def get_forks(self, repo_id):
322 if not isinstance(repo_id, int):
325 if not isinstance(repo_id, int):
323 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
326 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
324
327
325 return self.sa.query(Repository)\
328 return self.sa.query(Repository)\
326 .filter(Repository.fork_id == repo_id).count()
329 .filter(Repository.fork_id == repo_id).count()
327
330
328 def mark_as_fork(self, repo, fork, user):
331 def mark_as_fork(self, repo, fork, user):
329 repo = self.__get_repo(repo)
332 repo = self.__get_repo(repo)
330 fork = self.__get_repo(fork)
333 fork = self.__get_repo(fork)
331 repo.fork = fork
334 repo.fork = fork
332 self.sa.add(repo)
335 self.sa.add(repo)
333 return repo
336 return repo
334
337
335 def pull_changes(self, repo_name, username):
338 def pull_changes(self, repo_name, username):
336 dbrepo = Repository.get_by_repo_name(repo_name)
339 dbrepo = Repository.get_by_repo_name(repo_name)
337 clone_uri = dbrepo.clone_uri
340 clone_uri = dbrepo.clone_uri
338 if not clone_uri:
341 if not clone_uri:
339 raise Exception("This repository doesn't have a clone uri")
342 raise Exception("This repository doesn't have a clone uri")
340
343
341 repo = dbrepo.scm_instance
344 repo = dbrepo.scm_instance
342 try:
345 try:
343 extras = {'ip': '',
346 extras = {'ip': '',
344 'username': username,
347 'username': username,
345 'action': 'push_remote',
348 'action': 'push_remote',
346 'repository': repo_name}
349 'repository': repo_name}
347
350
348 #inject ui extra param to log this action via push logger
351 #inject ui extra param to log this action via push logger
349 for k, v in extras.items():
352 for k, v in extras.items():
350 repo._repo.ui.setconfig('rhodecode_extras', k, v)
353 repo._repo.ui.setconfig('rhodecode_extras', k, v)
351
354
352 repo.pull(clone_uri)
355 repo.pull(clone_uri)
353 self.mark_for_invalidation(repo_name)
356 self.mark_for_invalidation(repo_name)
354 except:
357 except:
355 log.error(traceback.format_exc())
358 log.error(traceback.format_exc())
356 raise
359 raise
357
360
358 def commit_change(self, repo, repo_name, cs, user, author, message,
361 def commit_change(self, repo, repo_name, cs, user, author, message,
359 content, f_path):
362 content, f_path):
360
363
361 if repo.alias == 'hg':
364 if repo.alias == 'hg':
362 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
365 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
363 elif repo.alias == 'git':
366 elif repo.alias == 'git':
364 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
367 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
365
368
366 # decoding here will force that we have proper encoded values
369 # decoding here will force that we have proper encoded values
367 # in any other case this will throw exceptions and deny commit
370 # in any other case this will throw exceptions and deny commit
368 content = safe_str(content)
371 content = safe_str(content)
369 message = safe_str(message)
372 message = safe_str(message)
370 path = safe_str(f_path)
373 path = safe_str(f_path)
371 author = safe_str(author)
374 author = safe_str(author)
372 m = IMC(repo)
375 m = IMC(repo)
373 m.change(FileNode(path, content))
376 m.change(FileNode(path, content))
374 tip = m.commit(message=message,
377 tip = m.commit(message=message,
375 author=author,
378 author=author,
376 parents=[cs], branch=cs.branch)
379 parents=[cs], branch=cs.branch)
377
380
378 new_cs = tip.short_id
381 new_cs = tip.short_id
379 action = 'push_local:%s' % new_cs
382 action = 'push_local:%s' % new_cs
380
383
381 action_logger(user, action, repo_name)
384 action_logger(user, action, repo_name)
382
385
383 self.mark_for_invalidation(repo_name)
386 self.mark_for_invalidation(repo_name)
384
387
385 def create_node(self, repo, repo_name, cs, user, author, message, content,
388 def create_node(self, repo, repo_name, cs, user, author, message, content,
386 f_path):
389 f_path):
387 if repo.alias == 'hg':
390 if repo.alias == 'hg':
388 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
391 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
389 elif repo.alias == 'git':
392 elif repo.alias == 'git':
390 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
393 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
391 # decoding here will force that we have proper encoded values
394 # decoding here will force that we have proper encoded values
392 # in any other case this will throw exceptions and deny commit
395 # in any other case this will throw exceptions and deny commit
393
396
394 if isinstance(content, (basestring,)):
397 if isinstance(content, (basestring,)):
395 content = safe_str(content)
398 content = safe_str(content)
396 elif isinstance(content, (file, cStringIO.OutputType,)):
399 elif isinstance(content, (file, cStringIO.OutputType,)):
397 content = content.read()
400 content = content.read()
398 else:
401 else:
399 raise Exception('Content is of unrecognized type %s' % (
402 raise Exception('Content is of unrecognized type %s' % (
400 type(content)
403 type(content)
401 ))
404 ))
402
405
403 message = safe_str(message)
406 message = safe_str(message)
404 path = safe_str(f_path)
407 path = safe_str(f_path)
405 author = safe_str(author)
408 author = safe_str(author)
406 m = IMC(repo)
409 m = IMC(repo)
407
410
408 if isinstance(cs, EmptyChangeset):
411 if isinstance(cs, EmptyChangeset):
409 # Emptychangeset means we we're editing empty repository
412 # Emptychangeset means we we're editing empty repository
410 parents = None
413 parents = None
411 else:
414 else:
412 parents = [cs]
415 parents = [cs]
413
416
414 m.add(FileNode(path, content=content))
417 m.add(FileNode(path, content=content))
415 tip = m.commit(message=message,
418 tip = m.commit(message=message,
416 author=author,
419 author=author,
417 parents=parents, branch=cs.branch)
420 parents=parents, branch=cs.branch)
418 new_cs = tip.short_id
421 new_cs = tip.short_id
419 action = 'push_local:%s' % new_cs
422 action = 'push_local:%s' % new_cs
420
423
421 action_logger(user, action, repo_name)
424 action_logger(user, action, repo_name)
422
425
423 self.mark_for_invalidation(repo_name)
426 self.mark_for_invalidation(repo_name)
424
427
425 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
428 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
426 """
429 """
427 recursive walk in root dir and return a set of all path in that dir
430 recursive walk in root dir and return a set of all path in that dir
428 based on repository walk function
431 based on repository walk function
429
432
430 :param repo_name: name of repository
433 :param repo_name: name of repository
431 :param revision: revision for which to list nodes
434 :param revision: revision for which to list nodes
432 :param root_path: root path to list
435 :param root_path: root path to list
433 :param flat: return as a list, if False returns a dict with decription
436 :param flat: return as a list, if False returns a dict with decription
434
437
435 """
438 """
436 _files = list()
439 _files = list()
437 _dirs = list()
440 _dirs = list()
438 try:
441 try:
439 _repo = self.__get_repo(repo_name)
442 _repo = self.__get_repo(repo_name)
440 changeset = _repo.scm_instance.get_changeset(revision)
443 changeset = _repo.scm_instance.get_changeset(revision)
441 root_path = root_path.lstrip('/')
444 root_path = root_path.lstrip('/')
442 for topnode, dirs, files in changeset.walk(root_path):
445 for topnode, dirs, files in changeset.walk(root_path):
443 for f in files:
446 for f in files:
444 _files.append(f.path if flat else {"name": f.path,
447 _files.append(f.path if flat else {"name": f.path,
445 "type": "file"})
448 "type": "file"})
446 for d in dirs:
449 for d in dirs:
447 _dirs.append(d.path if flat else {"name": d.path,
450 _dirs.append(d.path if flat else {"name": d.path,
448 "type": "dir"})
451 "type": "dir"})
449 except RepositoryError:
452 except RepositoryError:
450 log.debug(traceback.format_exc())
453 log.debug(traceback.format_exc())
451 raise
454 raise
452
455
453 return _dirs, _files
456 return _dirs, _files
454
457
455 def get_unread_journal(self):
458 def get_unread_journal(self):
456 return self.sa.query(UserLog).count()
459 return self.sa.query(UserLog).count()
@@ -1,124 +1,124 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repositories administration')} - ${c.rhodecode_name}
5 ${_('Repositories administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
10 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
11 </%def>
11 </%def>
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15 <%def name="main()">
15 <%def name="main()">
16 <div class="box">
16 <div class="box">
17
17
18 <div class="title">
18 <div class="title">
19 ${self.breadcrumbs()}
19 ${self.breadcrumbs()}
20 <ul class="links">
20 <ul class="links">
21 <li>
21 <li>
22 <span>${h.link_to(_(u'ADD REPOSITORY'),h.url('new_repo'))}</span>
22 <span>${h.link_to(_(u'ADD REPOSITORY'),h.url('new_repo'))}</span>
23 </li>
23 </li>
24 </ul>
24 </ul>
25 </div>
25 </div>
26
26
27 <div class="table">
27 <div class="table">
28 <div id='repos_list_wrap' class="yui-skin-sam">
28 <div id='repos_list_wrap' class="yui-skin-sam">
29 <%cnt=0%>
29 <%cnt=0%>
30 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
30 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
31
31
32 <table id="repos_list">
32 <table id="repos_list">
33 <thead>
33 <thead>
34 <tr>
34 <tr>
35 <th class="left"></th>
35 <th class="left"></th>
36 <th class="left">${_('Name')}</th>
36 <th class="left">${_('Name')}</th>
37 <th class="left">${_('Description')}</th>
37 <th class="left">${_('Description')}</th>
38 <th class="left">${_('Last change')}</th>
38 <th class="left">${_('Last change')}</th>
39 <th class="left">${_('Tip')}</th>
39 <th class="left">${_('Tip')}</th>
40 <th class="left">${_('Contact')}</th>
40 <th class="left">${_('Contact')}</th>
41 <th class="left">${_('Action')}</th>
41 <th class="left">${_('Action')}</th>
42 </tr>
42 </tr>
43 </thead>
43 </thead>
44
44
45 %for cnt,repo in enumerate(c.repos_list,1):
45 %for cnt,repo in enumerate(c.repos_list):
46 <tr class="parity${cnt%2}">
46 <tr class="parity${(cnt+1)%2}">
47 <td class="quick_repo_menu">
47 <td class="quick_repo_menu">
48 ${dt.quick_menu(repo['name'])}
48 ${dt.quick_menu(repo['name'])}
49 </td>
49 </td>
50 <td class="reponame">
50 <td class="reponame">
51 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
51 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
52 </td>
52 </td>
53 ##DESCRIPTION
53 ##DESCRIPTION
54 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
54 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
55 ${h.truncate(repo['description'],60)}</span>
55 ${h.truncate(repo['description'],60)}</span>
56 </td>
56 </td>
57 ##LAST CHANGE
57 ##LAST CHANGE
58 <td>
58 <td>
59 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
59 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
60 </td>
60 </td>
61 ##LAST REVISION
61 ##LAST REVISION
62 <td>
62 <td>
63 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
63 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
64 </td>
64 </td>
65 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
65 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
66 <td>
66 <td>
67 ${h.form(url('repo', repo_name=repo['name']),method='delete')}
67 ${h.form(url('repo', repo_name=repo['name']),method='delete')}
68 ${h.submit('remove_%s' % repo['name'],_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")}
68 ${h.submit('remove_%s' % repo['name'],_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo['name']+"');")}
69 ${h.end_form()}
69 ${h.end_form()}
70 </td>
70 </td>
71 </tr>
71 </tr>
72 %endfor
72 %endfor
73 </table>
73 </table>
74 </div>
74 </div>
75 </div>
75 </div>
76 </div>
76 </div>
77 <script>
77 <script>
78
78
79 // main table sorting
79 // main table sorting
80 var myColumnDefs = [
80 var myColumnDefs = [
81 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
81 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
82 {key:"name",label:"${_('Name')}",sortable:true,
82 {key:"name",label:"${_('Name')}",sortable:true,
83 sortOptions: { sortFunction: nameSort }},
83 sortOptions: { sortFunction: nameSort }},
84 {key:"desc",label:"${_('Description')}",sortable:true},
84 {key:"desc",label:"${_('Description')}",sortable:true},
85 {key:"last_change",label:"${_('Last Change')}",sortable:true,
85 {key:"last_change",label:"${_('Last Change')}",sortable:true,
86 sortOptions: { sortFunction: ageSort }},
86 sortOptions: { sortFunction: ageSort }},
87 {key:"tip",label:"${_('Tip')}",sortable:true,
87 {key:"tip",label:"${_('Tip')}",sortable:true,
88 sortOptions: { sortFunction: revisionSort }},
88 sortOptions: { sortFunction: revisionSort }},
89 {key:"owner",label:"${_('Owner')}",sortable:true},
89 {key:"owner",label:"${_('Owner')}",sortable:true},
90 {key:"action",label:"${_('Action')}",sortable:false},
90 {key:"action",label:"${_('Action')}",sortable:false},
91 ];
91 ];
92
92
93 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
93 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
94
94
95 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
95 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
96
96
97 myDataSource.responseSchema = {
97 myDataSource.responseSchema = {
98 fields: [
98 fields: [
99 {key:"menu"},
99 {key:"menu"},
100 {key:"name"},
100 {key:"name"},
101 {key:"desc"},
101 {key:"desc"},
102 {key:"last_change"},
102 {key:"last_change"},
103 {key:"tip"},
103 {key:"tip"},
104 {key:"owner"},
104 {key:"owner"},
105 {key:"action"},
105 {key:"action"},
106 ]
106 ]
107 };
107 };
108
108
109 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
109 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
110 {
110 {
111 sortedBy:{key:"name",dir:"asc"},
111 sortedBy:{key:"name",dir:"asc"},
112 MSG_SORTASC:"${_('Click to sort ascending')}",
112 MSG_SORTASC:"${_('Click to sort ascending')}",
113 MSG_SORTDESC:"${_('Click to sort descending')}",
113 MSG_SORTDESC:"${_('Click to sort descending')}",
114 MSG_EMPTY:"${_('No records found.')}",
114 MSG_EMPTY:"${_('No records found.')}",
115 MSG_ERROR:"${_('Data error.')}",
115 MSG_ERROR:"${_('Data error.')}",
116 MSG_LOADING:"${_('Loading...')}",
116 MSG_LOADING:"${_('Loading...')}",
117 }
117 }
118 );
118 );
119 myDataTable.subscribe('postRenderEvent',function(oArgs) {
119 myDataTable.subscribe('postRenderEvent',function(oArgs) {
120 tooltip_activate();
120 tooltip_activate();
121 quick_repo_menu();
121 quick_repo_menu();
122 });
122 });
123 </script>
123 </script>
124 </%def>
124 </%def>
@@ -1,197 +1,197 b''
1 <%page args="parent" />
1 <%page args="parent" />
2 <div class="box">
2 <div class="box">
3 <!-- box / title -->
3 <!-- box / title -->
4 <div class="title">
4 <div class="title">
5 <h5>
5 <h5>
6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
7 </h5>
7 </h5>
8 %if c.rhodecode_user.username != 'default':
8 %if c.rhodecode_user.username != 'default':
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 <ul class="links">
10 <ul class="links">
11 <li>
11 <li>
12 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
12 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
13 </li>
13 </li>
14 </ul>
14 </ul>
15 %endif
15 %endif
16 %endif
16 %endif
17 </div>
17 </div>
18 <!-- end box / title -->
18 <!-- end box / title -->
19 <div class="table">
19 <div class="table">
20 % if c.groups:
20 % if c.groups:
21 <div id='groups_list_wrap' class="yui-skin-sam">
21 <div id='groups_list_wrap' class="yui-skin-sam">
22 <table id="groups_list">
22 <table id="groups_list">
23 <thead>
23 <thead>
24 <tr>
24 <tr>
25 <th class="left"><a href="#">${_('Group name')}</a></th>
25 <th class="left"><a href="#">${_('Group name')}</a></th>
26 <th class="left"><a href="#">${_('Description')}</a></th>
26 <th class="left"><a href="#">${_('Description')}</a></th>
27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
28 </tr>
28 </tr>
29 </thead>
29 </thead>
30
30
31 ## REPO GROUPS
31 ## REPO GROUPS
32 % for gr in c.groups:
32 % for gr in c.groups:
33 <tr>
33 <tr>
34 <td>
34 <td>
35 <div style="white-space: nowrap">
35 <div style="white-space: nowrap">
36 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
36 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
37 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
37 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
38 </div>
38 </div>
39 </td>
39 </td>
40 <td>${gr.group_description}</td>
40 <td>${gr.group_description}</td>
41 ## this is commented out since for multi nested repos can be HEAVY!
41 ## this is commented out since for multi nested repos can be HEAVY!
42 ## in number of executed queries during traversing uncomment at will
42 ## in number of executed queries during traversing uncomment at will
43 ##<td><b>${gr.repositories_recursive_count}</b></td>
43 ##<td><b>${gr.repositories_recursive_count}</b></td>
44 </tr>
44 </tr>
45 % endfor
45 % endfor
46
46
47 </table>
47 </table>
48 </div>
48 </div>
49 <div style="height: 20px"></div>
49 <div style="height: 20px"></div>
50 % endif
50 % endif
51 <div id="welcome" style="display:none;text-align:center">
51 <div id="welcome" style="display:none;text-align:center">
52 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
52 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
53 </div>
53 </div>
54 <div id='repos_list_wrap' class="yui-skin-sam">
54 <div id='repos_list_wrap' class="yui-skin-sam">
55 <%cnt=0%>
55 <%cnt=0%>
56 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
56 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
57
57
58 <table id="repos_list">
58 <table id="repos_list">
59 <thead>
59 <thead>
60 <tr>
60 <tr>
61 <th class="left"></th>
61 <th class="left"></th>
62 <th class="left">${_('Name')}</th>
62 <th class="left">${_('Name')}</th>
63 <th class="left">${_('Description')}</th>
63 <th class="left">${_('Description')}</th>
64 <th class="left">${_('Last change')}</th>
64 <th class="left">${_('Last change')}</th>
65 <th class="left">${_('Tip')}</th>
65 <th class="left">${_('Tip')}</th>
66 <th class="left">${_('Owner')}</th>
66 <th class="left">${_('Owner')}</th>
67 <th class="left">${_('RSS')}</th>
67 <th class="left">${_('RSS')}</th>
68 <th class="left">${_('Atom')}</th>
68 <th class="left">${_('Atom')}</th>
69 </tr>
69 </tr>
70 </thead>
70 </thead>
71 <tbody>
71 <tbody>
72 %for cnt,repo in enumerate(c.repos_list,1):
72 %for cnt,repo in enumerate(c.repos_list):
73 <tr class="parity${cnt%2}">
73 <tr class="parity${(cnt+1)%2}">
74 ##QUICK MENU
74 ##QUICK MENU
75 <td class="quick_repo_menu">
75 <td class="quick_repo_menu">
76 ${dt.quick_menu(repo['name'])}
76 ${dt.quick_menu(repo['name'])}
77 </td>
77 </td>
78 ##REPO NAME AND ICONS
78 ##REPO NAME AND ICONS
79 <td class="reponame">
79 <td class="reponame">
80 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
80 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
81 </td>
81 </td>
82 ##DESCRIPTION
82 ##DESCRIPTION
83 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
83 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
84 ${h.truncate(repo['description'],60)}</span>
84 ${h.truncate(repo['description'],60)}</span>
85 </td>
85 </td>
86 ##LAST CHANGE DATE
86 ##LAST CHANGE DATE
87 <td>
87 <td>
88 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
88 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
89 </td>
89 </td>
90 ##LAST REVISION
90 ##LAST REVISION
91 <td>
91 <td>
92 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
92 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
93 </td>
93 </td>
94 ##
94 ##
95 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
95 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
96 <td>
96 <td>
97 %if c.rhodecode_user.username != 'default':
97 %if c.rhodecode_user.username != 'default':
98 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
98 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
99 %else:
99 %else:
100 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
100 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
101 %endif:
101 %endif:
102 </td>
102 </td>
103 <td>
103 <td>
104 %if c.rhodecode_user.username != 'default':
104 %if c.rhodecode_user.username != 'default':
105 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
105 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
106 %else:
106 %else:
107 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
107 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
108 %endif:
108 %endif:
109 </td>
109 </td>
110 </tr>
110 </tr>
111 %endfor
111 %endfor
112 </tbody>
112 </tbody>
113 </table>
113 </table>
114 </div>
114 </div>
115 </div>
115 </div>
116 </div>
116 </div>
117 <script>
117 <script>
118 YUD.get('repo_count').innerHTML = ${cnt};
118 YUD.get('repo_count').innerHTML = ${cnt+1};
119 var func = function(node){
119 var func = function(node){
120 return node.parentNode.parentNode.parentNode.parentNode;
120 return node.parentNode.parentNode.parentNode.parentNode;
121 }
121 }
122
122
123
123
124 // groups table sorting
124 // groups table sorting
125 var myColumnDefs = [
125 var myColumnDefs = [
126 {key:"name",label:"${_('Group Name')}",sortable:true,
126 {key:"name",label:"${_('Group Name')}",sortable:true,
127 sortOptions: { sortFunction: groupNameSort }},
127 sortOptions: { sortFunction: groupNameSort }},
128 {key:"desc",label:"${_('Description')}",sortable:true},
128 {key:"desc",label:"${_('Description')}",sortable:true},
129 ];
129 ];
130
130
131 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
131 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
132
132
133 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
133 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
134 myDataSource.responseSchema = {
134 myDataSource.responseSchema = {
135 fields: [
135 fields: [
136 {key:"name"},
136 {key:"name"},
137 {key:"desc"},
137 {key:"desc"},
138 ]
138 ]
139 };
139 };
140
140
141 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
141 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
142 {
142 {
143 sortedBy:{key:"name",dir:"asc"},
143 sortedBy:{key:"name",dir:"asc"},
144 MSG_SORTASC:"${_('Click to sort ascending')}",
144 MSG_SORTASC:"${_('Click to sort ascending')}",
145 MSG_SORTDESC:"${_('Click to sort descending')}"
145 MSG_SORTDESC:"${_('Click to sort descending')}"
146 }
146 }
147 );
147 );
148
148
149 // main table sorting
149 // main table sorting
150 var myColumnDefs = [
150 var myColumnDefs = [
151 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
151 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
152 {key:"name",label:"${_('Name')}",sortable:true,
152 {key:"name",label:"${_('Name')}",sortable:true,
153 sortOptions: { sortFunction: nameSort }},
153 sortOptions: { sortFunction: nameSort }},
154 {key:"desc",label:"${_('Description')}",sortable:true},
154 {key:"desc",label:"${_('Description')}",sortable:true},
155 {key:"last_change",label:"${_('Last Change')}",sortable:true,
155 {key:"last_change",label:"${_('Last Change')}",sortable:true,
156 sortOptions: { sortFunction: ageSort }},
156 sortOptions: { sortFunction: ageSort }},
157 {key:"tip",label:"${_('Tip')}",sortable:true,
157 {key:"tip",label:"${_('Tip')}",sortable:true,
158 sortOptions: { sortFunction: revisionSort }},
158 sortOptions: { sortFunction: revisionSort }},
159 {key:"owner",label:"${_('Owner')}",sortable:true},
159 {key:"owner",label:"${_('Owner')}",sortable:true},
160 {key:"rss",label:"",sortable:false},
160 {key:"rss",label:"",sortable:false},
161 {key:"atom",label:"",sortable:false},
161 {key:"atom",label:"",sortable:false},
162 ];
162 ];
163
163
164 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
164 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
165
165
166 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
166 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
167
167
168 myDataSource.responseSchema = {
168 myDataSource.responseSchema = {
169 fields: [
169 fields: [
170 {key:"menu"},
170 {key:"menu"},
171 {key:"name"},
171 {key:"name"},
172 {key:"desc"},
172 {key:"desc"},
173 {key:"last_change"},
173 {key:"last_change"},
174 {key:"tip"},
174 {key:"tip"},
175 {key:"owner"},
175 {key:"owner"},
176 {key:"rss"},
176 {key:"rss"},
177 {key:"atom"},
177 {key:"atom"},
178 ]
178 ]
179 };
179 };
180
180
181 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
181 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
182 {
182 {
183 sortedBy:{key:"name",dir:"asc"},
183 sortedBy:{key:"name",dir:"asc"},
184 MSG_SORTASC:"${_('Click to sort ascending')}",
184 MSG_SORTASC:"${_('Click to sort ascending')}",
185 MSG_SORTDESC:"${_('Click to sort descending')}",
185 MSG_SORTDESC:"${_('Click to sort descending')}",
186 MSG_EMPTY:"${_('No records found.')}",
186 MSG_EMPTY:"${_('No records found.')}",
187 MSG_ERROR:"${_('Data error.')}",
187 MSG_ERROR:"${_('Data error.')}",
188 MSG_LOADING:"${_('Loading...')}",
188 MSG_LOADING:"${_('Loading...')}",
189 }
189 }
190 );
190 );
191 myDataTable.subscribe('postRenderEvent',function(oArgs) {
191 myDataTable.subscribe('postRenderEvent',function(oArgs) {
192 tooltip_activate();
192 tooltip_activate();
193 quick_repo_menu();
193 quick_repo_menu();
194 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
194 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
195 });
195 });
196
196
197 </script>
197 </script>
General Comments 0
You need to be logged in to leave comments. Login now