##// END OF EJS Templates
fixes for issue #148
marcink -
r1196:49ea5274 beta
parent child Browse files
Show More
@@ -1,688 +1,688
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) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import os
28 import os
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import traceback
31 import traceback
32 import paste
32 import paste
33 import beaker
33 import beaker
34
34
35 from paste.script.command import Command, BadCommand
35 from paste.script.command import Command, BadCommand
36
36
37 from UserDict import DictMixin
37 from UserDict import DictMixin
38
38
39 from mercurial import ui, config, hg
39 from mercurial import ui, config, hg
40 from mercurial.error import RepoError
40 from mercurial.error import RepoError
41
41
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43
43
44 from vcs.backends.base import BaseChangeset
44 from vcs.backends.base import BaseChangeset
45 from vcs.utils.lazy import LazyProperty
45 from vcs.utils.lazy import LazyProperty
46
46
47 from rhodecode.model import meta
47 from rhodecode.model import meta
48 from rhodecode.model.caching_query import FromCache
48 from rhodecode.model.caching_query import FromCache
49 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group
49 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.user import UserModel
51 from rhodecode.model.user import UserModel
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 def recursive_replace(str, replace=' '):
56 def recursive_replace(str, replace=' '):
57 """Recursive replace of given sign to just one instance
57 """Recursive replace of given sign to just one instance
58
58
59 :param str: given string
59 :param str: given string
60 :param replace: char to find and replace multiple instances
60 :param replace: char to find and replace multiple instances
61
61
62 Examples::
62 Examples::
63 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
63 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
64 'Mighty-Mighty-Bo-sstones'
64 'Mighty-Mighty-Bo-sstones'
65 """
65 """
66
66
67 if str.find(replace * 2) == -1:
67 if str.find(replace * 2) == -1:
68 return str
68 return str
69 else:
69 else:
70 str = str.replace(replace * 2, replace)
70 str = str.replace(replace * 2, replace)
71 return recursive_replace(str, replace)
71 return recursive_replace(str, replace)
72
72
73 def repo_name_slug(value):
73 def repo_name_slug(value):
74 """Return slug of name of repository
74 """Return slug of name of repository
75 This function is called on each creation/modification
75 This function is called on each creation/modification
76 of repository to prevent bad names in repo
76 of repository to prevent bad names in repo
77 """
77 """
78
78
79 slug = remove_formatting(value)
79 slug = remove_formatting(value)
80 slug = strip_tags(slug)
80 slug = strip_tags(slug)
81
81
82 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
82 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
83 slug = slug.replace(c, '-')
83 slug = slug.replace(c, '-')
84 slug = recursive_replace(slug, '-')
84 slug = recursive_replace(slug, '-')
85 slug = collapse(slug, '-')
85 slug = collapse(slug, '-')
86 return slug
86 return slug
87
87
88 def get_repo_slug(request):
88 def get_repo_slug(request):
89 return request.environ['pylons.routes_dict'].get('repo_name')
89 return request.environ['pylons.routes_dict'].get('repo_name')
90
90
91 def action_logger(user, action, repo, ipaddr='', sa=None):
91 def action_logger(user, action, repo, ipaddr='', sa=None):
92 """
92 """
93 Action logger for various actions made by users
93 Action logger for various actions made by users
94
94
95 :param user: user that made this action, can be a unique username string or
95 :param user: user that made this action, can be a unique username string or
96 object containing user_id attribute
96 object containing user_id attribute
97 :param action: action to log, should be on of predefined unique actions for
97 :param action: action to log, should be on of predefined unique actions for
98 easy translations
98 easy translations
99 :param repo: string name of repository or object containing repo_id,
99 :param repo: string name of repository or object containing repo_id,
100 that action was made on
100 that action was made on
101 :param ipaddr: optional ip address from what the action was made
101 :param ipaddr: optional ip address from what the action was made
102 :param sa: optional sqlalchemy session
102 :param sa: optional sqlalchemy session
103
103
104 """
104 """
105
105
106 if not sa:
106 if not sa:
107 sa = meta.Session()
107 sa = meta.Session()
108
108
109 try:
109 try:
110 um = UserModel()
110 um = UserModel()
111 if hasattr(user, 'user_id'):
111 if hasattr(user, 'user_id'):
112 user_obj = user
112 user_obj = user
113 elif isinstance(user, basestring):
113 elif isinstance(user, basestring):
114 user_obj = um.get_by_username(user, cache=False)
114 user_obj = um.get_by_username(user, cache=False)
115 else:
115 else:
116 raise Exception('You have to provide user object or username')
116 raise Exception('You have to provide user object or username')
117
117
118
118
119 rm = RepoModel()
119 rm = RepoModel()
120 if hasattr(repo, 'repo_id'):
120 if hasattr(repo, 'repo_id'):
121 repo_obj = rm.get(repo.repo_id, cache=False)
121 repo_obj = rm.get(repo.repo_id, cache=False)
122 repo_name = repo_obj.repo_name
122 repo_name = repo_obj.repo_name
123 elif isinstance(repo, basestring):
123 elif isinstance(repo, basestring):
124 repo_name = repo.lstrip('/')
124 repo_name = repo.lstrip('/')
125 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
125 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
126 else:
126 else:
127 raise Exception('You have to provide repository to action logger')
127 raise Exception('You have to provide repository to action logger')
128
128
129
129
130 user_log = UserLog()
130 user_log = UserLog()
131 user_log.user_id = user_obj.user_id
131 user_log.user_id = user_obj.user_id
132 user_log.action = action
132 user_log.action = action
133
133
134 user_log.repository_id = repo_obj.repo_id
134 user_log.repository_id = repo_obj.repo_id
135 user_log.repository_name = repo_name
135 user_log.repository_name = repo_name
136
136
137 user_log.action_date = datetime.datetime.now()
137 user_log.action_date = datetime.datetime.now()
138 user_log.user_ip = ipaddr
138 user_log.user_ip = ipaddr
139 sa.add(user_log)
139 sa.add(user_log)
140 sa.commit()
140 sa.commit()
141
141
142 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
142 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
143 except:
143 except:
144 log.error(traceback.format_exc())
144 log.error(traceback.format_exc())
145 sa.rollback()
145 sa.rollback()
146
146
147 def get_repos(path, recursive=False):
147 def get_repos(path, recursive=False):
148 """
148 """
149 Scans given path for repos and return (name,(type,path)) tuple
149 Scans given path for repos and return (name,(type,path)) tuple
150
150
151 :param path: path to scann for repositories
151 :param path: path to scann for repositories
152 :param recursive: recursive search and return names with subdirs in front
152 :param recursive: recursive search and return names with subdirs in front
153 """
153 """
154 from vcs.utils.helpers import get_scm
154 from vcs.utils.helpers import get_scm
155 from vcs.exceptions import VCSError
155 from vcs.exceptions import VCSError
156
156
157 if path.endswith('/'):
157 if path.endswith('/'):
158 #add ending slash for better results
158 #add ending slash for better results
159 path = path[:-1]
159 path = path[:-1]
160
160
161 def _get_repos(p):
161 def _get_repos(p):
162 for dirpath in os.listdir(p):
162 for dirpath in os.listdir(p):
163 if os.path.isfile(os.path.join(p, dirpath)):
163 if os.path.isfile(os.path.join(p, dirpath)):
164 continue
164 continue
165 cur_path = os.path.join(p, dirpath)
165 cur_path = os.path.join(p, dirpath)
166 try:
166 try:
167 scm_info = get_scm(cur_path)
167 scm_info = get_scm(cur_path)
168 yield scm_info[1].split(path)[-1].lstrip('/'), scm_info
168 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
169 except VCSError:
169 except VCSError:
170 if not recursive:
170 if not recursive:
171 continue
171 continue
172 #check if this dir containts other repos for recursive scan
172 #check if this dir containts other repos for recursive scan
173 rec_path = os.path.join(p, dirpath)
173 rec_path = os.path.join(p, dirpath)
174 if os.path.isdir(rec_path):
174 if os.path.isdir(rec_path):
175 for inner_scm in _get_repos(rec_path):
175 for inner_scm in _get_repos(rec_path):
176 yield inner_scm
176 yield inner_scm
177
177
178 return _get_repos(path)
178 return _get_repos(path)
179
179
180 def check_repo_fast(repo_name, base_path):
180 def check_repo_fast(repo_name, base_path):
181 """
181 """
182 Check given path for existence of directory
182 Check given path for existence of directory
183 :param repo_name:
183 :param repo_name:
184 :param base_path:
184 :param base_path:
185
185
186 :return False: if this directory is present
186 :return False: if this directory is present
187 """
187 """
188 if os.path.isdir(os.path.join(base_path, repo_name)):return False
188 if os.path.isdir(os.path.join(base_path, repo_name)):return False
189 return True
189 return True
190
190
191 def check_repo(repo_name, base_path, verify=True):
191 def check_repo(repo_name, base_path, verify=True):
192
192
193 repo_path = os.path.join(base_path, repo_name)
193 repo_path = os.path.join(base_path, repo_name)
194
194
195 try:
195 try:
196 if not check_repo_fast(repo_name, base_path):
196 if not check_repo_fast(repo_name, base_path):
197 return False
197 return False
198 r = hg.repository(ui.ui(), repo_path)
198 r = hg.repository(ui.ui(), repo_path)
199 if verify:
199 if verify:
200 hg.verify(r)
200 hg.verify(r)
201 #here we hnow that repo exists it was verified
201 #here we hnow that repo exists it was verified
202 log.info('%s repo is already created', repo_name)
202 log.info('%s repo is already created', repo_name)
203 return False
203 return False
204 except RepoError:
204 except RepoError:
205 #it means that there is no valid repo there...
205 #it means that there is no valid repo there...
206 log.info('%s repo is free for creation', repo_name)
206 log.info('%s repo is free for creation', repo_name)
207 return True
207 return True
208
208
209 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
209 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
210 while True:
210 while True:
211 ok = raw_input(prompt)
211 ok = raw_input(prompt)
212 if ok in ('y', 'ye', 'yes'): return True
212 if ok in ('y', 'ye', 'yes'): return True
213 if ok in ('n', 'no', 'nop', 'nope'): return False
213 if ok in ('n', 'no', 'nop', 'nope'): return False
214 retries = retries - 1
214 retries = retries - 1
215 if retries < 0: raise IOError
215 if retries < 0: raise IOError
216 print complaint
216 print complaint
217
217
218 #propagated from mercurial documentation
218 #propagated from mercurial documentation
219 ui_sections = ['alias', 'auth',
219 ui_sections = ['alias', 'auth',
220 'decode/encode', 'defaults',
220 'decode/encode', 'defaults',
221 'diff', 'email',
221 'diff', 'email',
222 'extensions', 'format',
222 'extensions', 'format',
223 'merge-patterns', 'merge-tools',
223 'merge-patterns', 'merge-tools',
224 'hooks', 'http_proxy',
224 'hooks', 'http_proxy',
225 'smtp', 'patch',
225 'smtp', 'patch',
226 'paths', 'profiling',
226 'paths', 'profiling',
227 'server', 'trusted',
227 'server', 'trusted',
228 'ui', 'web', ]
228 'ui', 'web', ]
229
229
230 def make_ui(read_from='file', path=None, checkpaths=True):
230 def make_ui(read_from='file', path=None, checkpaths=True):
231 """A function that will read python rc files or database
231 """A function that will read python rc files or database
232 and make an mercurial ui object from read options
232 and make an mercurial ui object from read options
233
233
234 :param path: path to mercurial config file
234 :param path: path to mercurial config file
235 :param checkpaths: check the path
235 :param checkpaths: check the path
236 :param read_from: read from 'file' or 'db'
236 :param read_from: read from 'file' or 'db'
237 """
237 """
238
238
239 baseui = ui.ui()
239 baseui = ui.ui()
240
240
241 #clean the baseui object
241 #clean the baseui object
242 baseui._ocfg = config.config()
242 baseui._ocfg = config.config()
243 baseui._ucfg = config.config()
243 baseui._ucfg = config.config()
244 baseui._tcfg = config.config()
244 baseui._tcfg = config.config()
245
245
246 if read_from == 'file':
246 if read_from == 'file':
247 if not os.path.isfile(path):
247 if not os.path.isfile(path):
248 log.warning('Unable to read config file %s' % path)
248 log.warning('Unable to read config file %s' % path)
249 return False
249 return False
250 log.debug('reading hgrc from %s', path)
250 log.debug('reading hgrc from %s', path)
251 cfg = config.config()
251 cfg = config.config()
252 cfg.read(path)
252 cfg.read(path)
253 for section in ui_sections:
253 for section in ui_sections:
254 for k, v in cfg.items(section):
254 for k, v in cfg.items(section):
255 log.debug('settings ui from file[%s]%s:%s', section, k, v)
255 log.debug('settings ui from file[%s]%s:%s', section, k, v)
256 baseui.setconfig(section, k, v)
256 baseui.setconfig(section, k, v)
257
257
258
258
259 elif read_from == 'db':
259 elif read_from == 'db':
260 sa = meta.Session()
260 sa = meta.Session()
261 ret = sa.query(RhodeCodeUi)\
261 ret = sa.query(RhodeCodeUi)\
262 .options(FromCache("sql_cache_short",
262 .options(FromCache("sql_cache_short",
263 "get_hg_ui_settings")).all()
263 "get_hg_ui_settings")).all()
264
264
265 hg_ui = ret
265 hg_ui = ret
266 for ui_ in hg_ui:
266 for ui_ in hg_ui:
267 if ui_.ui_active:
267 if ui_.ui_active:
268 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
268 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
269 ui_.ui_key, ui_.ui_value)
269 ui_.ui_key, ui_.ui_value)
270 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
270 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
271
271
272 meta.Session.remove()
272 meta.Session.remove()
273 return baseui
273 return baseui
274
274
275
275
276 def set_rhodecode_config(config):
276 def set_rhodecode_config(config):
277 """Updates pylons config with new settings from database
277 """Updates pylons config with new settings from database
278
278
279 :param config:
279 :param config:
280 """
280 """
281 from rhodecode.model.settings import SettingsModel
281 from rhodecode.model.settings import SettingsModel
282 hgsettings = SettingsModel().get_app_settings()
282 hgsettings = SettingsModel().get_app_settings()
283
283
284 for k, v in hgsettings.items():
284 for k, v in hgsettings.items():
285 config[k] = v
285 config[k] = v
286
286
287 def invalidate_cache(cache_key, *args):
287 def invalidate_cache(cache_key, *args):
288 """Puts cache invalidation task into db for
288 """Puts cache invalidation task into db for
289 further global cache invalidation
289 further global cache invalidation
290 """
290 """
291
291
292 from rhodecode.model.scm import ScmModel
292 from rhodecode.model.scm import ScmModel
293
293
294 if cache_key.startswith('get_repo_cached_'):
294 if cache_key.startswith('get_repo_cached_'):
295 name = cache_key.split('get_repo_cached_')[-1]
295 name = cache_key.split('get_repo_cached_')[-1]
296 ScmModel().mark_for_invalidation(name)
296 ScmModel().mark_for_invalidation(name)
297
297
298 class EmptyChangeset(BaseChangeset):
298 class EmptyChangeset(BaseChangeset):
299 """
299 """
300 An dummy empty changeset. It's possible to pass hash when creating
300 An dummy empty changeset. It's possible to pass hash when creating
301 an EmptyChangeset
301 an EmptyChangeset
302 """
302 """
303
303
304 def __init__(self, cs='0' * 40):
304 def __init__(self, cs='0' * 40):
305 self._empty_cs = cs
305 self._empty_cs = cs
306 self.revision = -1
306 self.revision = -1
307 self.message = ''
307 self.message = ''
308 self.author = ''
308 self.author = ''
309 self.date = ''
309 self.date = ''
310
310
311 @LazyProperty
311 @LazyProperty
312 def raw_id(self):
312 def raw_id(self):
313 """Returns raw string identifying this changeset, useful for web
313 """Returns raw string identifying this changeset, useful for web
314 representation.
314 representation.
315 """
315 """
316
316
317 return self._empty_cs
317 return self._empty_cs
318
318
319 @LazyProperty
319 @LazyProperty
320 def short_id(self):
320 def short_id(self):
321 return self.raw_id[:12]
321 return self.raw_id[:12]
322
322
323 def get_file_changeset(self, path):
323 def get_file_changeset(self, path):
324 return self
324 return self
325
325
326 def get_file_content(self, path):
326 def get_file_content(self, path):
327 return u''
327 return u''
328
328
329 def get_file_size(self, path):
329 def get_file_size(self, path):
330 return 0
330 return 0
331
331
332 def map_groups(groups):
332 def map_groups(groups):
333 """Checks for groups existence, and creates groups structures.
333 """Checks for groups existence, and creates groups structures.
334 It returns last group in structure
334 It returns last group in structure
335
335
336 :param groups: list of groups structure
336 :param groups: list of groups structure
337 """
337 """
338 sa = meta.Session()
338 sa = meta.Session()
339
339
340 parent = None
340 parent = None
341 group = None
341 group = None
342 for lvl, group_name in enumerate(groups[:-1]):
342 for lvl, group_name in enumerate(groups[:-1]):
343 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
343 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
344
344
345 if group is None:
345 if group is None:
346 group = Group(group_name, parent)
346 group = Group(group_name, parent)
347 sa.add(group)
347 sa.add(group)
348 sa.commit()
348 sa.commit()
349
349
350 parent = group
350 parent = group
351
351
352 return group
352 return group
353
353
354 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
354 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
355 """maps all repos given in initial_repo_list, non existing repositories
355 """maps all repos given in initial_repo_list, non existing repositories
356 are created, if remove_obsolete is True it also check for db entries
356 are created, if remove_obsolete is True it also check for db entries
357 that are not in initial_repo_list and removes them.
357 that are not in initial_repo_list and removes them.
358
358
359 :param initial_repo_list: list of repositories found by scanning methods
359 :param initial_repo_list: list of repositories found by scanning methods
360 :param remove_obsolete: check for obsolete entries in database
360 :param remove_obsolete: check for obsolete entries in database
361 """
361 """
362
362
363 sa = meta.Session()
363 sa = meta.Session()
364 rm = RepoModel()
364 rm = RepoModel()
365 user = sa.query(User).filter(User.admin == True).first()
365 user = sa.query(User).filter(User.admin == True).first()
366 added = []
366 added = []
367 for name, repo in initial_repo_list.items():
367 for name, repo in initial_repo_list.items():
368 group = map_groups(name.split('/'))
368 group = map_groups(name.split('/'))
369 if not rm.get_by_repo_name(name, cache=False):
369 if not rm.get_by_repo_name(name, cache=False):
370 log.info('repository %s not found creating default', name)
370 log.info('repository %s not found creating default', name)
371 added.append(name)
371 added.append(name)
372 form_data = {
372 form_data = {
373 'repo_name':name,
373 'repo_name':name,
374 'repo_type':repo.alias,
374 'repo_type':repo.alias,
375 'description':repo.description \
375 'description':repo.description \
376 if repo.description != 'unknown' else \
376 if repo.description != 'unknown' else \
377 '%s repository' % name,
377 '%s repository' % name,
378 'private':False,
378 'private':False,
379 'group_id':getattr(group, 'group_id', None)
379 'group_id':getattr(group, 'group_id', None)
380 }
380 }
381 rm.create(form_data, user, just_db=True)
381 rm.create(form_data, user, just_db=True)
382
382
383 removed = []
383 removed = []
384 if remove_obsolete:
384 if remove_obsolete:
385 #remove from database those repositories that are not in the filesystem
385 #remove from database those repositories that are not in the filesystem
386 for repo in sa.query(Repository).all():
386 for repo in sa.query(Repository).all():
387 if repo.repo_name not in initial_repo_list.keys():
387 if repo.repo_name not in initial_repo_list.keys():
388 removed.append(repo.repo_name)
388 removed.append(repo.repo_name)
389 sa.delete(repo)
389 sa.delete(repo)
390 sa.commit()
390 sa.commit()
391
391
392 return added, removed
392 return added, removed
393 class OrderedDict(dict, DictMixin):
393 class OrderedDict(dict, DictMixin):
394
394
395 def __init__(self, *args, **kwds):
395 def __init__(self, *args, **kwds):
396 if len(args) > 1:
396 if len(args) > 1:
397 raise TypeError('expected at most 1 arguments, got %d' % len(args))
397 raise TypeError('expected at most 1 arguments, got %d' % len(args))
398 try:
398 try:
399 self.__end
399 self.__end
400 except AttributeError:
400 except AttributeError:
401 self.clear()
401 self.clear()
402 self.update(*args, **kwds)
402 self.update(*args, **kwds)
403
403
404 def clear(self):
404 def clear(self):
405 self.__end = end = []
405 self.__end = end = []
406 end += [None, end, end] # sentinel node for doubly linked list
406 end += [None, end, end] # sentinel node for doubly linked list
407 self.__map = {} # key --> [key, prev, next]
407 self.__map = {} # key --> [key, prev, next]
408 dict.clear(self)
408 dict.clear(self)
409
409
410 def __setitem__(self, key, value):
410 def __setitem__(self, key, value):
411 if key not in self:
411 if key not in self:
412 end = self.__end
412 end = self.__end
413 curr = end[1]
413 curr = end[1]
414 curr[2] = end[1] = self.__map[key] = [key, curr, end]
414 curr[2] = end[1] = self.__map[key] = [key, curr, end]
415 dict.__setitem__(self, key, value)
415 dict.__setitem__(self, key, value)
416
416
417 def __delitem__(self, key):
417 def __delitem__(self, key):
418 dict.__delitem__(self, key)
418 dict.__delitem__(self, key)
419 key, prev, next = self.__map.pop(key)
419 key, prev, next = self.__map.pop(key)
420 prev[2] = next
420 prev[2] = next
421 next[1] = prev
421 next[1] = prev
422
422
423 def __iter__(self):
423 def __iter__(self):
424 end = self.__end
424 end = self.__end
425 curr = end[2]
425 curr = end[2]
426 while curr is not end:
426 while curr is not end:
427 yield curr[0]
427 yield curr[0]
428 curr = curr[2]
428 curr = curr[2]
429
429
430 def __reversed__(self):
430 def __reversed__(self):
431 end = self.__end
431 end = self.__end
432 curr = end[1]
432 curr = end[1]
433 while curr is not end:
433 while curr is not end:
434 yield curr[0]
434 yield curr[0]
435 curr = curr[1]
435 curr = curr[1]
436
436
437 def popitem(self, last=True):
437 def popitem(self, last=True):
438 if not self:
438 if not self:
439 raise KeyError('dictionary is empty')
439 raise KeyError('dictionary is empty')
440 if last:
440 if last:
441 key = reversed(self).next()
441 key = reversed(self).next()
442 else:
442 else:
443 key = iter(self).next()
443 key = iter(self).next()
444 value = self.pop(key)
444 value = self.pop(key)
445 return key, value
445 return key, value
446
446
447 def __reduce__(self):
447 def __reduce__(self):
448 items = [[k, self[k]] for k in self]
448 items = [[k, self[k]] for k in self]
449 tmp = self.__map, self.__end
449 tmp = self.__map, self.__end
450 del self.__map, self.__end
450 del self.__map, self.__end
451 inst_dict = vars(self).copy()
451 inst_dict = vars(self).copy()
452 self.__map, self.__end = tmp
452 self.__map, self.__end = tmp
453 if inst_dict:
453 if inst_dict:
454 return (self.__class__, (items,), inst_dict)
454 return (self.__class__, (items,), inst_dict)
455 return self.__class__, (items,)
455 return self.__class__, (items,)
456
456
457 def keys(self):
457 def keys(self):
458 return list(self)
458 return list(self)
459
459
460 setdefault = DictMixin.setdefault
460 setdefault = DictMixin.setdefault
461 update = DictMixin.update
461 update = DictMixin.update
462 pop = DictMixin.pop
462 pop = DictMixin.pop
463 values = DictMixin.values
463 values = DictMixin.values
464 items = DictMixin.items
464 items = DictMixin.items
465 iterkeys = DictMixin.iterkeys
465 iterkeys = DictMixin.iterkeys
466 itervalues = DictMixin.itervalues
466 itervalues = DictMixin.itervalues
467 iteritems = DictMixin.iteritems
467 iteritems = DictMixin.iteritems
468
468
469 def __repr__(self):
469 def __repr__(self):
470 if not self:
470 if not self:
471 return '%s()' % (self.__class__.__name__,)
471 return '%s()' % (self.__class__.__name__,)
472 return '%s(%r)' % (self.__class__.__name__, self.items())
472 return '%s(%r)' % (self.__class__.__name__, self.items())
473
473
474 def copy(self):
474 def copy(self):
475 return self.__class__(self)
475 return self.__class__(self)
476
476
477 @classmethod
477 @classmethod
478 def fromkeys(cls, iterable, value=None):
478 def fromkeys(cls, iterable, value=None):
479 d = cls()
479 d = cls()
480 for key in iterable:
480 for key in iterable:
481 d[key] = value
481 d[key] = value
482 return d
482 return d
483
483
484 def __eq__(self, other):
484 def __eq__(self, other):
485 if isinstance(other, OrderedDict):
485 if isinstance(other, OrderedDict):
486 return len(self) == len(other) and self.items() == other.items()
486 return len(self) == len(other) and self.items() == other.items()
487 return dict.__eq__(self, other)
487 return dict.__eq__(self, other)
488
488
489 def __ne__(self, other):
489 def __ne__(self, other):
490 return not self == other
490 return not self == other
491
491
492
492
493 #set cache regions for beaker so celery can utilise it
493 #set cache regions for beaker so celery can utilise it
494 def add_cache(settings):
494 def add_cache(settings):
495 cache_settings = {'regions':None}
495 cache_settings = {'regions':None}
496 for key in settings.keys():
496 for key in settings.keys():
497 for prefix in ['beaker.cache.', 'cache.']:
497 for prefix in ['beaker.cache.', 'cache.']:
498 if key.startswith(prefix):
498 if key.startswith(prefix):
499 name = key.split(prefix)[1].strip()
499 name = key.split(prefix)[1].strip()
500 cache_settings[name] = settings[key].strip()
500 cache_settings[name] = settings[key].strip()
501 if cache_settings['regions']:
501 if cache_settings['regions']:
502 for region in cache_settings['regions'].split(','):
502 for region in cache_settings['regions'].split(','):
503 region = region.strip()
503 region = region.strip()
504 region_settings = {}
504 region_settings = {}
505 for key, value in cache_settings.items():
505 for key, value in cache_settings.items():
506 if key.startswith(region):
506 if key.startswith(region):
507 region_settings[key.split('.')[1]] = value
507 region_settings[key.split('.')[1]] = value
508 region_settings['expire'] = int(region_settings.get('expire',
508 region_settings['expire'] = int(region_settings.get('expire',
509 60))
509 60))
510 region_settings.setdefault('lock_dir',
510 region_settings.setdefault('lock_dir',
511 cache_settings.get('lock_dir'))
511 cache_settings.get('lock_dir'))
512 region_settings.setdefault('data_dir',
512 region_settings.setdefault('data_dir',
513 cache_settings.get('data_dir'))
513 cache_settings.get('data_dir'))
514
514
515 if 'type' not in region_settings:
515 if 'type' not in region_settings:
516 region_settings['type'] = cache_settings.get('type',
516 region_settings['type'] = cache_settings.get('type',
517 'memory')
517 'memory')
518 beaker.cache.cache_regions[region] = region_settings
518 beaker.cache.cache_regions[region] = region_settings
519
519
520 def get_current_revision():
520 def get_current_revision():
521 """Returns tuple of (number, id) from repository containing this package
521 """Returns tuple of (number, id) from repository containing this package
522 or None if repository could not be found.
522 or None if repository could not be found.
523 """
523 """
524
524
525 try:
525 try:
526 from vcs import get_repo
526 from vcs import get_repo
527 from vcs.utils.helpers import get_scm
527 from vcs.utils.helpers import get_scm
528 from vcs.exceptions import RepositoryError, VCSError
528 from vcs.exceptions import RepositoryError, VCSError
529 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
529 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
530 scm = get_scm(repopath)[0]
530 scm = get_scm(repopath)[0]
531 repo = get_repo(path=repopath, alias=scm)
531 repo = get_repo(path=repopath, alias=scm)
532 tip = repo.get_changeset()
532 tip = repo.get_changeset()
533 return (tip.revision, tip.short_id)
533 return (tip.revision, tip.short_id)
534 except (ImportError, RepositoryError, VCSError), err:
534 except (ImportError, RepositoryError, VCSError), err:
535 logging.debug("Cannot retrieve rhodecode's revision. Original error "
535 logging.debug("Cannot retrieve rhodecode's revision. Original error "
536 "was: %s" % err)
536 "was: %s" % err)
537 return None
537 return None
538
538
539 #===============================================================================
539 #===============================================================================
540 # TEST FUNCTIONS AND CREATORS
540 # TEST FUNCTIONS AND CREATORS
541 #===============================================================================
541 #===============================================================================
542 def create_test_index(repo_location, full_index):
542 def create_test_index(repo_location, full_index):
543 """Makes default test index
543 """Makes default test index
544 :param repo_location:
544 :param repo_location:
545 :param full_index:
545 :param full_index:
546 """
546 """
547 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
547 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
548 from rhodecode.lib.pidlock import DaemonLock, LockHeld
548 from rhodecode.lib.pidlock import DaemonLock, LockHeld
549 import shutil
549 import shutil
550
550
551 index_location = os.path.join(repo_location, 'index')
551 index_location = os.path.join(repo_location, 'index')
552 if os.path.exists(index_location):
552 if os.path.exists(index_location):
553 shutil.rmtree(index_location)
553 shutil.rmtree(index_location)
554
554
555 try:
555 try:
556 l = DaemonLock()
556 l = DaemonLock()
557 WhooshIndexingDaemon(index_location=index_location,
557 WhooshIndexingDaemon(index_location=index_location,
558 repo_location=repo_location)\
558 repo_location=repo_location)\
559 .run(full_index=full_index)
559 .run(full_index=full_index)
560 l.release()
560 l.release()
561 except LockHeld:
561 except LockHeld:
562 pass
562 pass
563
563
564 def create_test_env(repos_test_path, config):
564 def create_test_env(repos_test_path, config):
565 """Makes a fresh database and
565 """Makes a fresh database and
566 install test repository into tmp dir
566 install test repository into tmp dir
567 """
567 """
568 from rhodecode.lib.db_manage import DbManage
568 from rhodecode.lib.db_manage import DbManage
569 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
569 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
570 HG_FORK, GIT_FORK, TESTS_TMP_PATH
570 HG_FORK, GIT_FORK, TESTS_TMP_PATH
571 import tarfile
571 import tarfile
572 import shutil
572 import shutil
573 from os.path import dirname as dn, join as jn, abspath
573 from os.path import dirname as dn, join as jn, abspath
574
574
575 log = logging.getLogger('TestEnvCreator')
575 log = logging.getLogger('TestEnvCreator')
576 # create logger
576 # create logger
577 log.setLevel(logging.DEBUG)
577 log.setLevel(logging.DEBUG)
578 log.propagate = True
578 log.propagate = True
579 # create console handler and set level to debug
579 # create console handler and set level to debug
580 ch = logging.StreamHandler()
580 ch = logging.StreamHandler()
581 ch.setLevel(logging.DEBUG)
581 ch.setLevel(logging.DEBUG)
582
582
583 # create formatter
583 # create formatter
584 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
584 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
585
585
586 # add formatter to ch
586 # add formatter to ch
587 ch.setFormatter(formatter)
587 ch.setFormatter(formatter)
588
588
589 # add ch to logger
589 # add ch to logger
590 log.addHandler(ch)
590 log.addHandler(ch)
591
591
592 #PART ONE create db
592 #PART ONE create db
593 dbconf = config['sqlalchemy.db1.url']
593 dbconf = config['sqlalchemy.db1.url']
594 log.debug('making test db %s', dbconf)
594 log.debug('making test db %s', dbconf)
595
595
596 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
596 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
597 tests=True)
597 tests=True)
598 dbmanage.create_tables(override=True)
598 dbmanage.create_tables(override=True)
599 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
599 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
600 dbmanage.create_default_user()
600 dbmanage.create_default_user()
601 dbmanage.admin_prompt()
601 dbmanage.admin_prompt()
602 dbmanage.create_permissions()
602 dbmanage.create_permissions()
603 dbmanage.populate_default_permissions()
603 dbmanage.populate_default_permissions()
604
604
605 #PART TWO make test repo
605 #PART TWO make test repo
606 log.debug('making test vcs repositories')
606 log.debug('making test vcs repositories')
607
607
608 #remove old one from previos tests
608 #remove old one from previos tests
609 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
609 for r in [HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, HG_FORK, GIT_FORK]:
610
610
611 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
611 if os.path.isdir(jn(TESTS_TMP_PATH, r)):
612 log.debug('removing %s', r)
612 log.debug('removing %s', r)
613 shutil.rmtree(jn(TESTS_TMP_PATH, r))
613 shutil.rmtree(jn(TESTS_TMP_PATH, r))
614
614
615 #CREATE DEFAULT HG REPOSITORY
615 #CREATE DEFAULT HG REPOSITORY
616 cur_dir = dn(dn(abspath(__file__)))
616 cur_dir = dn(dn(abspath(__file__)))
617 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
617 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
618 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
618 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
619 tar.close()
619 tar.close()
620
620
621
621
622 #==============================================================================
622 #==============================================================================
623 # PASTER COMMANDS
623 # PASTER COMMANDS
624 #==============================================================================
624 #==============================================================================
625
625
626 class BasePasterCommand(Command):
626 class BasePasterCommand(Command):
627 """
627 """
628 Abstract Base Class for paster commands.
628 Abstract Base Class for paster commands.
629
629
630 The celery commands are somewhat aggressive about loading
630 The celery commands are somewhat aggressive about loading
631 celery.conf, and since our module sets the `CELERY_LOADER`
631 celery.conf, and since our module sets the `CELERY_LOADER`
632 environment variable to our loader, we have to bootstrap a bit and
632 environment variable to our loader, we have to bootstrap a bit and
633 make sure we've had a chance to load the pylons config off of the
633 make sure we've had a chance to load the pylons config off of the
634 command line, otherwise everything fails.
634 command line, otherwise everything fails.
635 """
635 """
636 min_args = 1
636 min_args = 1
637 min_args_error = "Please provide a paster config file as an argument."
637 min_args_error = "Please provide a paster config file as an argument."
638 takes_config_file = 1
638 takes_config_file = 1
639 requires_config_file = True
639 requires_config_file = True
640
640
641 def notify_msg(self, msg, log=False):
641 def notify_msg(self, msg, log=False):
642 """Make a notification to user, additionally if logger is passed
642 """Make a notification to user, additionally if logger is passed
643 it logs this action using given logger
643 it logs this action using given logger
644
644
645 :param msg: message that will be printed to user
645 :param msg: message that will be printed to user
646 :param log: logging instance, to use to additionally log this message
646 :param log: logging instance, to use to additionally log this message
647
647
648 """
648 """
649 if log and isinstance(log, logging):
649 if log and isinstance(log, logging):
650 log(msg)
650 log(msg)
651
651
652
652
653 def run(self, args):
653 def run(self, args):
654 """
654 """
655 Overrides Command.run
655 Overrides Command.run
656
656
657 Checks for a config file argument and loads it.
657 Checks for a config file argument and loads it.
658 """
658 """
659 if len(args) < self.min_args:
659 if len(args) < self.min_args:
660 raise BadCommand(
660 raise BadCommand(
661 self.min_args_error % {'min_args': self.min_args,
661 self.min_args_error % {'min_args': self.min_args,
662 'actual_args': len(args)})
662 'actual_args': len(args)})
663
663
664 # Decrement because we're going to lob off the first argument.
664 # Decrement because we're going to lob off the first argument.
665 # @@ This is hacky
665 # @@ This is hacky
666 self.min_args -= 1
666 self.min_args -= 1
667 self.bootstrap_config(args[0])
667 self.bootstrap_config(args[0])
668 self.update_parser()
668 self.update_parser()
669 return super(BasePasterCommand, self).run(args[1:])
669 return super(BasePasterCommand, self).run(args[1:])
670
670
671 def update_parser(self):
671 def update_parser(self):
672 """
672 """
673 Abstract method. Allows for the class's parser to be updated
673 Abstract method. Allows for the class's parser to be updated
674 before the superclass's `run` method is called. Necessary to
674 before the superclass's `run` method is called. Necessary to
675 allow options/arguments to be passed through to the underlying
675 allow options/arguments to be passed through to the underlying
676 celery command.
676 celery command.
677 """
677 """
678 raise NotImplementedError("Abstract Method.")
678 raise NotImplementedError("Abstract Method.")
679
679
680 def bootstrap_config(self, conf):
680 def bootstrap_config(self, conf):
681 """
681 """
682 Loads the pylons configuration.
682 Loads the pylons configuration.
683 """
683 """
684 from pylons import config as pylonsconfig
684 from pylons import config as pylonsconfig
685
685
686 path_to_ini_file = os.path.realpath(conf)
686 path_to_ini_file = os.path.realpath(conf)
687 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
687 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
688 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
688 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
General Comments 0
You need to be logged in to leave comments. Login now