##// END OF EJS Templates
logging: make 'Creating a cache key for...' more readable
Mads Kiilerich -
r3134:ff315659 beta
parent child Browse files
Show More
@@ -1,755 +1,755 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 re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 import decorator
35 import decorator
36 import warnings
36 import warnings
37 from os.path import abspath
37 from os.path import abspath
38 from os.path import dirname as dn, join as jn
38 from os.path import dirname as dn, join as jn
39
39
40 from paste.script.command import Command, BadCommand
40 from paste.script.command import Command, BadCommand
41
41
42 from mercurial import ui, config
42 from mercurial import ui, config
43
43
44 from webhelpers.text import collapse, remove_formatting, strip_tags
44 from webhelpers.text import collapse, remove_formatting, strip_tags
45
45
46 from rhodecode.lib.vcs import get_backend
46 from rhodecode.lib.vcs import get_backend
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 from rhodecode.lib.vcs.utils.helpers import get_scm
49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.exceptions import VCSError
51
51
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model import meta
54 from rhodecode.model import meta
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 from rhodecode.model.meta import Session
57 from rhodecode.model.meta import Session
58 from rhodecode.model.repos_group import ReposGroupModel
58 from rhodecode.model.repos_group import ReposGroupModel
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 from rhodecode.lib.vcs.utils.fakemod import create_module
60 from rhodecode.lib.vcs.utils.fakemod import create_module
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65
65
66
66
67 def recursive_replace(str_, replace=' '):
67 def recursive_replace(str_, replace=' '):
68 """
68 """
69 Recursive replace of given sign to just one instance
69 Recursive replace of given sign to just one instance
70
70
71 :param str_: given string
71 :param str_: given string
72 :param replace: char to find and replace multiple instances
72 :param replace: char to find and replace multiple instances
73
73
74 Examples::
74 Examples::
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 'Mighty-Mighty-Bo-sstones'
76 'Mighty-Mighty-Bo-sstones'
77 """
77 """
78
78
79 if str_.find(replace * 2) == -1:
79 if str_.find(replace * 2) == -1:
80 return str_
80 return str_
81 else:
81 else:
82 str_ = str_.replace(replace * 2, replace)
82 str_ = str_.replace(replace * 2, replace)
83 return recursive_replace(str_, replace)
83 return recursive_replace(str_, replace)
84
84
85
85
86 def repo_name_slug(value):
86 def repo_name_slug(value):
87 """
87 """
88 Return slug of name of repository
88 Return slug of name of repository
89 This function is called on each creation/modification
89 This function is called on each creation/modification
90 of repository to prevent bad names in repo
90 of repository to prevent bad names in repo
91 """
91 """
92
92
93 slug = remove_formatting(value)
93 slug = remove_formatting(value)
94 slug = strip_tags(slug)
94 slug = strip_tags(slug)
95
95
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 slug = slug.replace(c, '-')
97 slug = slug.replace(c, '-')
98 slug = recursive_replace(slug, '-')
98 slug = recursive_replace(slug, '-')
99 slug = collapse(slug, '-')
99 slug = collapse(slug, '-')
100 return slug
100 return slug
101
101
102
102
103 def get_repo_slug(request):
103 def get_repo_slug(request):
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 if _repo:
105 if _repo:
106 _repo = _repo.rstrip('/')
106 _repo = _repo.rstrip('/')
107 return _repo
107 return _repo
108
108
109
109
110 def get_repos_group_slug(request):
110 def get_repos_group_slug(request):
111 _group = request.environ['pylons.routes_dict'].get('group_name')
111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 if _group:
112 if _group:
113 _group = _group.rstrip('/')
113 _group = _group.rstrip('/')
114 return _group
114 return _group
115
115
116
116
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 """
118 """
119 Action logger for various actions made by users
119 Action logger for various actions made by users
120
120
121 :param user: user that made this action, can be a unique username string or
121 :param user: user that made this action, can be a unique username string or
122 object containing user_id attribute
122 object containing user_id attribute
123 :param action: action to log, should be on of predefined unique actions for
123 :param action: action to log, should be on of predefined unique actions for
124 easy translations
124 easy translations
125 :param repo: string name of repository or object containing repo_id,
125 :param repo: string name of repository or object containing repo_id,
126 that action was made on
126 that action was made on
127 :param ipaddr: optional ip address from what the action was made
127 :param ipaddr: optional ip address from what the action was made
128 :param sa: optional sqlalchemy session
128 :param sa: optional sqlalchemy session
129
129
130 """
130 """
131
131
132 if not sa:
132 if not sa:
133 sa = meta.Session()
133 sa = meta.Session()
134
134
135 try:
135 try:
136 if hasattr(user, 'user_id'):
136 if hasattr(user, 'user_id'):
137 user_obj = User.get(user.user_id)
137 user_obj = User.get(user.user_id)
138 elif isinstance(user, basestring):
138 elif isinstance(user, basestring):
139 user_obj = User.get_by_username(user)
139 user_obj = User.get_by_username(user)
140 else:
140 else:
141 raise Exception('You have to provide a user object or a username')
141 raise Exception('You have to provide a user object or a username')
142
142
143 if hasattr(repo, 'repo_id'):
143 if hasattr(repo, 'repo_id'):
144 repo_obj = Repository.get(repo.repo_id)
144 repo_obj = Repository.get(repo.repo_id)
145 repo_name = repo_obj.repo_name
145 repo_name = repo_obj.repo_name
146 elif isinstance(repo, basestring):
146 elif isinstance(repo, basestring):
147 repo_name = repo.lstrip('/')
147 repo_name = repo.lstrip('/')
148 repo_obj = Repository.get_by_repo_name(repo_name)
148 repo_obj = Repository.get_by_repo_name(repo_name)
149 else:
149 else:
150 repo_obj = None
150 repo_obj = None
151 repo_name = ''
151 repo_name = ''
152
152
153 user_log = UserLog()
153 user_log = UserLog()
154 user_log.user_id = user_obj.user_id
154 user_log.user_id = user_obj.user_id
155 user_log.username = user_obj.username
155 user_log.username = user_obj.username
156 user_log.action = safe_unicode(action)
156 user_log.action = safe_unicode(action)
157
157
158 user_log.repository = repo_obj
158 user_log.repository = repo_obj
159 user_log.repository_name = repo_name
159 user_log.repository_name = repo_name
160
160
161 user_log.action_date = datetime.datetime.now()
161 user_log.action_date = datetime.datetime.now()
162 user_log.user_ip = ipaddr
162 user_log.user_ip = ipaddr
163 sa.add(user_log)
163 sa.add(user_log)
164
164
165 log.info(
165 log.info(
166 'Adding user %s, action %s on %s' % (user_obj, action,
166 'Adding user %s, action %s on %s' % (user_obj, action,
167 safe_unicode(repo))
167 safe_unicode(repo))
168 )
168 )
169 if commit:
169 if commit:
170 sa.commit()
170 sa.commit()
171 except:
171 except:
172 log.error(traceback.format_exc())
172 log.error(traceback.format_exc())
173 raise
173 raise
174
174
175
175
176 def get_repos(path, recursive=False):
176 def get_repos(path, recursive=False):
177 """
177 """
178 Scans given path for repos and return (name,(type,path)) tuple
178 Scans given path for repos and return (name,(type,path)) tuple
179
179
180 :param path: path to scan for repositories
180 :param path: path to scan for repositories
181 :param recursive: recursive search and return names with subdirs in front
181 :param recursive: recursive search and return names with subdirs in front
182 """
182 """
183
183
184 # remove ending slash for better results
184 # remove ending slash for better results
185 path = path.rstrip(os.sep)
185 path = path.rstrip(os.sep)
186
186
187 def _get_repos(p):
187 def _get_repos(p):
188 if not os.access(p, os.W_OK):
188 if not os.access(p, os.W_OK):
189 return
189 return
190 for dirpath in os.listdir(p):
190 for dirpath in os.listdir(p):
191 if os.path.isfile(os.path.join(p, dirpath)):
191 if os.path.isfile(os.path.join(p, dirpath)):
192 continue
192 continue
193 cur_path = os.path.join(p, dirpath)
193 cur_path = os.path.join(p, dirpath)
194 try:
194 try:
195 scm_info = get_scm(cur_path)
195 scm_info = get_scm(cur_path)
196 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
196 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
197 except VCSError:
197 except VCSError:
198 if not recursive:
198 if not recursive:
199 continue
199 continue
200 #check if this dir containts other repos for recursive scan
200 #check if this dir containts other repos for recursive scan
201 rec_path = os.path.join(p, dirpath)
201 rec_path = os.path.join(p, dirpath)
202 if os.path.isdir(rec_path):
202 if os.path.isdir(rec_path):
203 for inner_scm in _get_repos(rec_path):
203 for inner_scm in _get_repos(rec_path):
204 yield inner_scm
204 yield inner_scm
205
205
206 return _get_repos(path)
206 return _get_repos(path)
207
207
208
208
209 def is_valid_repo(repo_name, base_path, scm=None):
209 def is_valid_repo(repo_name, base_path, scm=None):
210 """
210 """
211 Returns True if given path is a valid repository False otherwise.
211 Returns True if given path is a valid repository False otherwise.
212 If scm param is given also compare if given scm is the same as expected
212 If scm param is given also compare if given scm is the same as expected
213 from scm parameter
213 from scm parameter
214
214
215 :param repo_name:
215 :param repo_name:
216 :param base_path:
216 :param base_path:
217 :param scm:
217 :param scm:
218
218
219 :return True: if given path is a valid repository
219 :return True: if given path is a valid repository
220 """
220 """
221 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
221 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
222
222
223 try:
223 try:
224 scm_ = get_scm(full_path)
224 scm_ = get_scm(full_path)
225 if scm:
225 if scm:
226 return scm_[0] == scm
226 return scm_[0] == scm
227 return True
227 return True
228 except VCSError:
228 except VCSError:
229 return False
229 return False
230
230
231
231
232 def is_valid_repos_group(repos_group_name, base_path):
232 def is_valid_repos_group(repos_group_name, base_path):
233 """
233 """
234 Returns True if given path is a repos group False otherwise
234 Returns True if given path is a repos group False otherwise
235
235
236 :param repo_name:
236 :param repo_name:
237 :param base_path:
237 :param base_path:
238 """
238 """
239 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
239 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
240
240
241 # check if it's not a repo
241 # check if it's not a repo
242 if is_valid_repo(repos_group_name, base_path):
242 if is_valid_repo(repos_group_name, base_path):
243 return False
243 return False
244
244
245 try:
245 try:
246 # we need to check bare git repos at higher level
246 # we need to check bare git repos at higher level
247 # since we might match branches/hooks/info/objects or possible
247 # since we might match branches/hooks/info/objects or possible
248 # other things inside bare git repo
248 # other things inside bare git repo
249 get_scm(os.path.dirname(full_path))
249 get_scm(os.path.dirname(full_path))
250 return False
250 return False
251 except VCSError:
251 except VCSError:
252 pass
252 pass
253
253
254 # check if it's a valid path
254 # check if it's a valid path
255 if os.path.isdir(full_path):
255 if os.path.isdir(full_path):
256 return True
256 return True
257
257
258 return False
258 return False
259
259
260
260
261 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
261 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
262 while True:
262 while True:
263 ok = raw_input(prompt)
263 ok = raw_input(prompt)
264 if ok in ('y', 'ye', 'yes'):
264 if ok in ('y', 'ye', 'yes'):
265 return True
265 return True
266 if ok in ('n', 'no', 'nop', 'nope'):
266 if ok in ('n', 'no', 'nop', 'nope'):
267 return False
267 return False
268 retries = retries - 1
268 retries = retries - 1
269 if retries < 0:
269 if retries < 0:
270 raise IOError
270 raise IOError
271 print complaint
271 print complaint
272
272
273 #propagated from mercurial documentation
273 #propagated from mercurial documentation
274 ui_sections = ['alias', 'auth',
274 ui_sections = ['alias', 'auth',
275 'decode/encode', 'defaults',
275 'decode/encode', 'defaults',
276 'diff', 'email',
276 'diff', 'email',
277 'extensions', 'format',
277 'extensions', 'format',
278 'merge-patterns', 'merge-tools',
278 'merge-patterns', 'merge-tools',
279 'hooks', 'http_proxy',
279 'hooks', 'http_proxy',
280 'smtp', 'patch',
280 'smtp', 'patch',
281 'paths', 'profiling',
281 'paths', 'profiling',
282 'server', 'trusted',
282 'server', 'trusted',
283 'ui', 'web', ]
283 'ui', 'web', ]
284
284
285
285
286 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
286 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
287 """
287 """
288 A function that will read python rc files or database
288 A function that will read python rc files or database
289 and make an mercurial ui object from read options
289 and make an mercurial ui object from read options
290
290
291 :param path: path to mercurial config file
291 :param path: path to mercurial config file
292 :param checkpaths: check the path
292 :param checkpaths: check the path
293 :param read_from: read from 'file' or 'db'
293 :param read_from: read from 'file' or 'db'
294 """
294 """
295
295
296 baseui = ui.ui()
296 baseui = ui.ui()
297
297
298 # clean the baseui object
298 # clean the baseui object
299 baseui._ocfg = config.config()
299 baseui._ocfg = config.config()
300 baseui._ucfg = config.config()
300 baseui._ucfg = config.config()
301 baseui._tcfg = config.config()
301 baseui._tcfg = config.config()
302
302
303 if read_from == 'file':
303 if read_from == 'file':
304 if not os.path.isfile(path):
304 if not os.path.isfile(path):
305 log.debug('hgrc file is not present at %s, skipping...' % path)
305 log.debug('hgrc file is not present at %s, skipping...' % path)
306 return False
306 return False
307 log.debug('reading hgrc from %s' % path)
307 log.debug('reading hgrc from %s' % path)
308 cfg = config.config()
308 cfg = config.config()
309 cfg.read(path)
309 cfg.read(path)
310 for section in ui_sections:
310 for section in ui_sections:
311 for k, v in cfg.items(section):
311 for k, v in cfg.items(section):
312 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
312 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
313 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
313 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
314
314
315 elif read_from == 'db':
315 elif read_from == 'db':
316 sa = meta.Session()
316 sa = meta.Session()
317 ret = sa.query(RhodeCodeUi)\
317 ret = sa.query(RhodeCodeUi)\
318 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
318 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
319 .all()
319 .all()
320
320
321 hg_ui = ret
321 hg_ui = ret
322 for ui_ in hg_ui:
322 for ui_ in hg_ui:
323 if ui_.ui_active:
323 if ui_.ui_active:
324 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
324 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
325 ui_.ui_key, ui_.ui_value)
325 ui_.ui_key, ui_.ui_value)
326 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
326 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
327 safe_str(ui_.ui_value))
327 safe_str(ui_.ui_value))
328 if ui_.ui_key == 'push_ssl':
328 if ui_.ui_key == 'push_ssl':
329 # force set push_ssl requirement to False, rhodecode
329 # force set push_ssl requirement to False, rhodecode
330 # handles that
330 # handles that
331 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
331 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
332 False)
332 False)
333 if clear_session:
333 if clear_session:
334 meta.Session.remove()
334 meta.Session.remove()
335 return baseui
335 return baseui
336
336
337
337
338 def set_rhodecode_config(config):
338 def set_rhodecode_config(config):
339 """
339 """
340 Updates pylons config with new settings from database
340 Updates pylons config with new settings from database
341
341
342 :param config:
342 :param config:
343 """
343 """
344 hgsettings = RhodeCodeSetting.get_app_settings()
344 hgsettings = RhodeCodeSetting.get_app_settings()
345
345
346 for k, v in hgsettings.items():
346 for k, v in hgsettings.items():
347 config[k] = v
347 config[k] = v
348
348
349
349
350 def invalidate_cache(cache_key, *args):
350 def invalidate_cache(cache_key, *args):
351 """
351 """
352 Puts cache invalidation task into db for
352 Puts cache invalidation task into db for
353 further global cache invalidation
353 further global cache invalidation
354 """
354 """
355
355
356 from rhodecode.model.scm import ScmModel
356 from rhodecode.model.scm import ScmModel
357
357
358 if cache_key.startswith('get_repo_cached_'):
358 if cache_key.startswith('get_repo_cached_'):
359 name = cache_key.split('get_repo_cached_')[-1]
359 name = cache_key.split('get_repo_cached_')[-1]
360 ScmModel().mark_for_invalidation(name)
360 ScmModel().mark_for_invalidation(name)
361
361
362
362
363 def map_groups(path):
363 def map_groups(path):
364 """
364 """
365 Given a full path to a repository, create all nested groups that this
365 Given a full path to a repository, create all nested groups that this
366 repo is inside. This function creates parent-child relationships between
366 repo is inside. This function creates parent-child relationships between
367 groups and creates default perms for all new groups.
367 groups and creates default perms for all new groups.
368
368
369 :param paths: full path to repository
369 :param paths: full path to repository
370 """
370 """
371 sa = meta.Session()
371 sa = meta.Session()
372 groups = path.split(Repository.url_sep())
372 groups = path.split(Repository.url_sep())
373 parent = None
373 parent = None
374 group = None
374 group = None
375
375
376 # last element is repo in nested groups structure
376 # last element is repo in nested groups structure
377 groups = groups[:-1]
377 groups = groups[:-1]
378 rgm = ReposGroupModel(sa)
378 rgm = ReposGroupModel(sa)
379 for lvl, group_name in enumerate(groups):
379 for lvl, group_name in enumerate(groups):
380 group_name = '/'.join(groups[:lvl] + [group_name])
380 group_name = '/'.join(groups[:lvl] + [group_name])
381 group = RepoGroup.get_by_group_name(group_name)
381 group = RepoGroup.get_by_group_name(group_name)
382 desc = '%s group' % group_name
382 desc = '%s group' % group_name
383
383
384 # skip folders that are now removed repos
384 # skip folders that are now removed repos
385 if REMOVED_REPO_PAT.match(group_name):
385 if REMOVED_REPO_PAT.match(group_name):
386 break
386 break
387
387
388 if group is None:
388 if group is None:
389 log.debug('creating group level: %s group_name: %s' % (lvl,
389 log.debug('creating group level: %s group_name: %s' % (lvl,
390 group_name))
390 group_name))
391 group = RepoGroup(group_name, parent)
391 group = RepoGroup(group_name, parent)
392 group.group_description = desc
392 group.group_description = desc
393 sa.add(group)
393 sa.add(group)
394 rgm._create_default_perms(group)
394 rgm._create_default_perms(group)
395 sa.flush()
395 sa.flush()
396 parent = group
396 parent = group
397 return group
397 return group
398
398
399
399
400 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
400 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
401 install_git_hook=False):
401 install_git_hook=False):
402 """
402 """
403 maps all repos given in initial_repo_list, non existing repositories
403 maps all repos given in initial_repo_list, non existing repositories
404 are created, if remove_obsolete is True it also check for db entries
404 are created, if remove_obsolete is True it also check for db entries
405 that are not in initial_repo_list and removes them.
405 that are not in initial_repo_list and removes them.
406
406
407 :param initial_repo_list: list of repositories found by scanning methods
407 :param initial_repo_list: list of repositories found by scanning methods
408 :param remove_obsolete: check for obsolete entries in database
408 :param remove_obsolete: check for obsolete entries in database
409 :param install_git_hook: if this is True, also check and install githook
409 :param install_git_hook: if this is True, also check and install githook
410 for a repo if missing
410 for a repo if missing
411 """
411 """
412 from rhodecode.model.repo import RepoModel
412 from rhodecode.model.repo import RepoModel
413 from rhodecode.model.scm import ScmModel
413 from rhodecode.model.scm import ScmModel
414 sa = meta.Session()
414 sa = meta.Session()
415 rm = RepoModel()
415 rm = RepoModel()
416 user = sa.query(User).filter(User.admin == True).first()
416 user = sa.query(User).filter(User.admin == True).first()
417 if user is None:
417 if user is None:
418 raise Exception('Missing administrative account!')
418 raise Exception('Missing administrative account!')
419 added = []
419 added = []
420
420
421 # # clear cache keys
421 # # clear cache keys
422 # log.debug("Clearing cache keys now...")
422 # log.debug("Clearing cache keys now...")
423 # CacheInvalidation.clear_cache()
423 # CacheInvalidation.clear_cache()
424 # sa.commit()
424 # sa.commit()
425
425
426 ##creation defaults
426 ##creation defaults
427 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
427 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
428 enable_statistics = defs.get('repo_enable_statistics')
428 enable_statistics = defs.get('repo_enable_statistics')
429 enable_locking = defs.get('repo_enable_locking')
429 enable_locking = defs.get('repo_enable_locking')
430 enable_downloads = defs.get('repo_enable_downloads')
430 enable_downloads = defs.get('repo_enable_downloads')
431 private = defs.get('repo_private')
431 private = defs.get('repo_private')
432
432
433 for name, repo in initial_repo_list.items():
433 for name, repo in initial_repo_list.items():
434 group = map_groups(name)
434 group = map_groups(name)
435 db_repo = rm.get_by_repo_name(name)
435 db_repo = rm.get_by_repo_name(name)
436 # found repo that is on filesystem not in RhodeCode database
436 # found repo that is on filesystem not in RhodeCode database
437 if not db_repo:
437 if not db_repo:
438 log.info('repository %s not found, creating now' % name)
438 log.info('repository %s not found, creating now' % name)
439 added.append(name)
439 added.append(name)
440 desc = (repo.description
440 desc = (repo.description
441 if repo.description != 'unknown'
441 if repo.description != 'unknown'
442 else '%s repository' % name)
442 else '%s repository' % name)
443
443
444 new_repo = rm.create_repo(
444 new_repo = rm.create_repo(
445 repo_name=name,
445 repo_name=name,
446 repo_type=repo.alias,
446 repo_type=repo.alias,
447 description=desc,
447 description=desc,
448 repos_group=getattr(group, 'group_id', None),
448 repos_group=getattr(group, 'group_id', None),
449 owner=user,
449 owner=user,
450 just_db=True,
450 just_db=True,
451 enable_locking=enable_locking,
451 enable_locking=enable_locking,
452 enable_downloads=enable_downloads,
452 enable_downloads=enable_downloads,
453 enable_statistics=enable_statistics,
453 enable_statistics=enable_statistics,
454 private=private
454 private=private
455 )
455 )
456 # we added that repo just now, and make sure it has githook
456 # we added that repo just now, and make sure it has githook
457 # installed
457 # installed
458 if new_repo.repo_type == 'git':
458 if new_repo.repo_type == 'git':
459 ScmModel().install_git_hook(new_repo.scm_instance)
459 ScmModel().install_git_hook(new_repo.scm_instance)
460 elif install_git_hook:
460 elif install_git_hook:
461 if db_repo.repo_type == 'git':
461 if db_repo.repo_type == 'git':
462 ScmModel().install_git_hook(db_repo.scm_instance)
462 ScmModel().install_git_hook(db_repo.scm_instance)
463 # during starting install all cache keys for all repositories in the
463 # during starting install all cache keys for all repositories in the
464 # system, this will register all repos and multiple instances
464 # system, this will register all repos and multiple instances
465 key, _prefix, _org_key = CacheInvalidation._get_key(name)
465 key, _prefix, _org_key = CacheInvalidation._get_key(name)
466 CacheInvalidation.invalidate(name)
466 CacheInvalidation.invalidate(name)
467 log.debug("Creating a cache key for %s instance_id=>`%s`"
467 log.debug("Creating a cache key for %s, instance_id %s"
468 % (name, _prefix or '-'))
468 % (name, _prefix or 'unknown'))
469
469
470 sa.commit()
470 sa.commit()
471 removed = []
471 removed = []
472 if remove_obsolete:
472 if remove_obsolete:
473 # remove from database those repositories that are not in the filesystem
473 # remove from database those repositories that are not in the filesystem
474 for repo in sa.query(Repository).all():
474 for repo in sa.query(Repository).all():
475 if repo.repo_name not in initial_repo_list.keys():
475 if repo.repo_name not in initial_repo_list.keys():
476 log.debug("Removing non-existing repository found in db `%s`" %
476 log.debug("Removing non-existing repository found in db `%s`" %
477 repo.repo_name)
477 repo.repo_name)
478 try:
478 try:
479 sa.delete(repo)
479 sa.delete(repo)
480 sa.commit()
480 sa.commit()
481 removed.append(repo.repo_name)
481 removed.append(repo.repo_name)
482 except:
482 except:
483 #don't hold further removals on error
483 #don't hold further removals on error
484 log.error(traceback.format_exc())
484 log.error(traceback.format_exc())
485 sa.rollback()
485 sa.rollback()
486
486
487 return added, removed
487 return added, removed
488
488
489
489
490 # set cache regions for beaker so celery can utilise it
490 # set cache regions for beaker so celery can utilise it
491 def add_cache(settings):
491 def add_cache(settings):
492 cache_settings = {'regions': None}
492 cache_settings = {'regions': None}
493 for key in settings.keys():
493 for key in settings.keys():
494 for prefix in ['beaker.cache.', 'cache.']:
494 for prefix in ['beaker.cache.', 'cache.']:
495 if key.startswith(prefix):
495 if key.startswith(prefix):
496 name = key.split(prefix)[1].strip()
496 name = key.split(prefix)[1].strip()
497 cache_settings[name] = settings[key].strip()
497 cache_settings[name] = settings[key].strip()
498 if cache_settings['regions']:
498 if cache_settings['regions']:
499 for region in cache_settings['regions'].split(','):
499 for region in cache_settings['regions'].split(','):
500 region = region.strip()
500 region = region.strip()
501 region_settings = {}
501 region_settings = {}
502 for key, value in cache_settings.items():
502 for key, value in cache_settings.items():
503 if key.startswith(region):
503 if key.startswith(region):
504 region_settings[key.split('.')[1]] = value
504 region_settings[key.split('.')[1]] = value
505 region_settings['expire'] = int(region_settings.get('expire',
505 region_settings['expire'] = int(region_settings.get('expire',
506 60))
506 60))
507 region_settings.setdefault('lock_dir',
507 region_settings.setdefault('lock_dir',
508 cache_settings.get('lock_dir'))
508 cache_settings.get('lock_dir'))
509 region_settings.setdefault('data_dir',
509 region_settings.setdefault('data_dir',
510 cache_settings.get('data_dir'))
510 cache_settings.get('data_dir'))
511
511
512 if 'type' not in region_settings:
512 if 'type' not in region_settings:
513 region_settings['type'] = cache_settings.get('type',
513 region_settings['type'] = cache_settings.get('type',
514 'memory')
514 'memory')
515 beaker.cache.cache_regions[region] = region_settings
515 beaker.cache.cache_regions[region] = region_settings
516
516
517
517
518 def load_rcextensions(root_path):
518 def load_rcextensions(root_path):
519 import rhodecode
519 import rhodecode
520 from rhodecode.config import conf
520 from rhodecode.config import conf
521
521
522 path = os.path.join(root_path, 'rcextensions', '__init__.py')
522 path = os.path.join(root_path, 'rcextensions', '__init__.py')
523 if os.path.isfile(path):
523 if os.path.isfile(path):
524 rcext = create_module('rc', path)
524 rcext = create_module('rc', path)
525 EXT = rhodecode.EXTENSIONS = rcext
525 EXT = rhodecode.EXTENSIONS = rcext
526 log.debug('Found rcextensions now loading %s...' % rcext)
526 log.debug('Found rcextensions now loading %s...' % rcext)
527
527
528 # Additional mappings that are not present in the pygments lexers
528 # Additional mappings that are not present in the pygments lexers
529 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
529 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
530
530
531 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
531 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
532
532
533 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
533 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
534 log.debug('settings custom INDEX_EXTENSIONS')
534 log.debug('settings custom INDEX_EXTENSIONS')
535 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
535 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
536
536
537 #ADDITIONAL MAPPINGS
537 #ADDITIONAL MAPPINGS
538 log.debug('adding extra into INDEX_EXTENSIONS')
538 log.debug('adding extra into INDEX_EXTENSIONS')
539 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
539 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
540
540
541
541
542 #==============================================================================
542 #==============================================================================
543 # TEST FUNCTIONS AND CREATORS
543 # TEST FUNCTIONS AND CREATORS
544 #==============================================================================
544 #==============================================================================
545 def create_test_index(repo_location, config, full_index):
545 def create_test_index(repo_location, config, full_index):
546 """
546 """
547 Makes default test index
547 Makes default test index
548
548
549 :param config: test config
549 :param config: test config
550 :param full_index:
550 :param full_index:
551 """
551 """
552
552
553 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
553 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
554 from rhodecode.lib.pidlock import DaemonLock, LockHeld
554 from rhodecode.lib.pidlock import DaemonLock, LockHeld
555
555
556 repo_location = repo_location
556 repo_location = repo_location
557
557
558 index_location = os.path.join(config['app_conf']['index_dir'])
558 index_location = os.path.join(config['app_conf']['index_dir'])
559 if not os.path.exists(index_location):
559 if not os.path.exists(index_location):
560 os.makedirs(index_location)
560 os.makedirs(index_location)
561
561
562 try:
562 try:
563 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
563 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
564 WhooshIndexingDaemon(index_location=index_location,
564 WhooshIndexingDaemon(index_location=index_location,
565 repo_location=repo_location)\
565 repo_location=repo_location)\
566 .run(full_index=full_index)
566 .run(full_index=full_index)
567 l.release()
567 l.release()
568 except LockHeld:
568 except LockHeld:
569 pass
569 pass
570
570
571
571
572 def create_test_env(repos_test_path, config):
572 def create_test_env(repos_test_path, config):
573 """
573 """
574 Makes a fresh database and
574 Makes a fresh database and
575 install test repository into tmp dir
575 install test repository into tmp dir
576 """
576 """
577 from rhodecode.lib.db_manage import DbManage
577 from rhodecode.lib.db_manage import DbManage
578 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
578 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
579
579
580 # PART ONE create db
580 # PART ONE create db
581 dbconf = config['sqlalchemy.db1.url']
581 dbconf = config['sqlalchemy.db1.url']
582 log.debug('making test db %s' % dbconf)
582 log.debug('making test db %s' % dbconf)
583
583
584 # create test dir if it doesn't exist
584 # create test dir if it doesn't exist
585 if not os.path.isdir(repos_test_path):
585 if not os.path.isdir(repos_test_path):
586 log.debug('Creating testdir %s' % repos_test_path)
586 log.debug('Creating testdir %s' % repos_test_path)
587 os.makedirs(repos_test_path)
587 os.makedirs(repos_test_path)
588
588
589 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
589 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
590 tests=True)
590 tests=True)
591 dbmanage.create_tables(override=True)
591 dbmanage.create_tables(override=True)
592 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
592 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
593 dbmanage.create_default_user()
593 dbmanage.create_default_user()
594 dbmanage.admin_prompt()
594 dbmanage.admin_prompt()
595 dbmanage.create_permissions()
595 dbmanage.create_permissions()
596 dbmanage.populate_default_permissions()
596 dbmanage.populate_default_permissions()
597 Session().commit()
597 Session().commit()
598 # PART TWO make test repo
598 # PART TWO make test repo
599 log.debug('making test vcs repositories')
599 log.debug('making test vcs repositories')
600
600
601 idx_path = config['app_conf']['index_dir']
601 idx_path = config['app_conf']['index_dir']
602 data_path = config['app_conf']['cache_dir']
602 data_path = config['app_conf']['cache_dir']
603
603
604 #clean index and data
604 #clean index and data
605 if idx_path and os.path.exists(idx_path):
605 if idx_path and os.path.exists(idx_path):
606 log.debug('remove %s' % idx_path)
606 log.debug('remove %s' % idx_path)
607 shutil.rmtree(idx_path)
607 shutil.rmtree(idx_path)
608
608
609 if data_path and os.path.exists(data_path):
609 if data_path and os.path.exists(data_path):
610 log.debug('remove %s' % data_path)
610 log.debug('remove %s' % data_path)
611 shutil.rmtree(data_path)
611 shutil.rmtree(data_path)
612
612
613 #CREATE DEFAULT TEST REPOS
613 #CREATE DEFAULT TEST REPOS
614 cur_dir = dn(dn(abspath(__file__)))
614 cur_dir = dn(dn(abspath(__file__)))
615 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
615 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
616 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
616 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
617 tar.close()
617 tar.close()
618
618
619 cur_dir = dn(dn(abspath(__file__)))
619 cur_dir = dn(dn(abspath(__file__)))
620 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
620 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
621 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
621 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
622 tar.close()
622 tar.close()
623
623
624 #LOAD VCS test stuff
624 #LOAD VCS test stuff
625 from rhodecode.tests.vcs import setup_package
625 from rhodecode.tests.vcs import setup_package
626 setup_package()
626 setup_package()
627
627
628
628
629 #==============================================================================
629 #==============================================================================
630 # PASTER COMMANDS
630 # PASTER COMMANDS
631 #==============================================================================
631 #==============================================================================
632 class BasePasterCommand(Command):
632 class BasePasterCommand(Command):
633 """
633 """
634 Abstract Base Class for paster commands.
634 Abstract Base Class for paster commands.
635
635
636 The celery commands are somewhat aggressive about loading
636 The celery commands are somewhat aggressive about loading
637 celery.conf, and since our module sets the `CELERY_LOADER`
637 celery.conf, and since our module sets the `CELERY_LOADER`
638 environment variable to our loader, we have to bootstrap a bit and
638 environment variable to our loader, we have to bootstrap a bit and
639 make sure we've had a chance to load the pylons config off of the
639 make sure we've had a chance to load the pylons config off of the
640 command line, otherwise everything fails.
640 command line, otherwise everything fails.
641 """
641 """
642 min_args = 1
642 min_args = 1
643 min_args_error = "Please provide a paster config file as an argument."
643 min_args_error = "Please provide a paster config file as an argument."
644 takes_config_file = 1
644 takes_config_file = 1
645 requires_config_file = True
645 requires_config_file = True
646
646
647 def notify_msg(self, msg, log=False):
647 def notify_msg(self, msg, log=False):
648 """Make a notification to user, additionally if logger is passed
648 """Make a notification to user, additionally if logger is passed
649 it logs this action using given logger
649 it logs this action using given logger
650
650
651 :param msg: message that will be printed to user
651 :param msg: message that will be printed to user
652 :param log: logging instance, to use to additionally log this message
652 :param log: logging instance, to use to additionally log this message
653
653
654 """
654 """
655 if log and isinstance(log, logging):
655 if log and isinstance(log, logging):
656 log(msg)
656 log(msg)
657
657
658 def run(self, args):
658 def run(self, args):
659 """
659 """
660 Overrides Command.run
660 Overrides Command.run
661
661
662 Checks for a config file argument and loads it.
662 Checks for a config file argument and loads it.
663 """
663 """
664 if len(args) < self.min_args:
664 if len(args) < self.min_args:
665 raise BadCommand(
665 raise BadCommand(
666 self.min_args_error % {'min_args': self.min_args,
666 self.min_args_error % {'min_args': self.min_args,
667 'actual_args': len(args)})
667 'actual_args': len(args)})
668
668
669 # Decrement because we're going to lob off the first argument.
669 # Decrement because we're going to lob off the first argument.
670 # @@ This is hacky
670 # @@ This is hacky
671 self.min_args -= 1
671 self.min_args -= 1
672 self.bootstrap_config(args[0])
672 self.bootstrap_config(args[0])
673 self.update_parser()
673 self.update_parser()
674 return super(BasePasterCommand, self).run(args[1:])
674 return super(BasePasterCommand, self).run(args[1:])
675
675
676 def update_parser(self):
676 def update_parser(self):
677 """
677 """
678 Abstract method. Allows for the class's parser to be updated
678 Abstract method. Allows for the class's parser to be updated
679 before the superclass's `run` method is called. Necessary to
679 before the superclass's `run` method is called. Necessary to
680 allow options/arguments to be passed through to the underlying
680 allow options/arguments to be passed through to the underlying
681 celery command.
681 celery command.
682 """
682 """
683 raise NotImplementedError("Abstract Method.")
683 raise NotImplementedError("Abstract Method.")
684
684
685 def bootstrap_config(self, conf):
685 def bootstrap_config(self, conf):
686 """
686 """
687 Loads the pylons configuration.
687 Loads the pylons configuration.
688 """
688 """
689 from pylons import config as pylonsconfig
689 from pylons import config as pylonsconfig
690
690
691 self.path_to_ini_file = os.path.realpath(conf)
691 self.path_to_ini_file = os.path.realpath(conf)
692 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
692 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
693 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
693 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
694
694
695
695
696 def check_git_version():
696 def check_git_version():
697 """
697 """
698 Checks what version of git is installed in system, and issues a warning
698 Checks what version of git is installed in system, and issues a warning
699 if it's too old for RhodeCode to properly work.
699 if it's too old for RhodeCode to properly work.
700 """
700 """
701 import subprocess
701 import subprocess
702 from distutils.version import StrictVersion
702 from distutils.version import StrictVersion
703 from rhodecode import BACKENDS
703 from rhodecode import BACKENDS
704
704
705 p = subprocess.Popen('git --version', shell=True,
705 p = subprocess.Popen('git --version', shell=True,
706 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
706 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
707 stdout, stderr = p.communicate()
707 stdout, stderr = p.communicate()
708 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
708 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
709 if len(ver.split('.')) > 3:
709 if len(ver.split('.')) > 3:
710 #StrictVersion needs to be only 3 element type
710 #StrictVersion needs to be only 3 element type
711 ver = '.'.join(ver.split('.')[:3])
711 ver = '.'.join(ver.split('.')[:3])
712 try:
712 try:
713 _ver = StrictVersion(ver)
713 _ver = StrictVersion(ver)
714 except:
714 except:
715 _ver = StrictVersion('0.0.0')
715 _ver = StrictVersion('0.0.0')
716 stderr = traceback.format_exc()
716 stderr = traceback.format_exc()
717
717
718 req_ver = '1.7.4'
718 req_ver = '1.7.4'
719 to_old_git = False
719 to_old_git = False
720 if _ver < StrictVersion(req_ver):
720 if _ver < StrictVersion(req_ver):
721 to_old_git = True
721 to_old_git = True
722
722
723 if 'git' in BACKENDS:
723 if 'git' in BACKENDS:
724 log.debug('GIT version detected: %s' % stdout)
724 log.debug('GIT version detected: %s' % stdout)
725 if stderr:
725 if stderr:
726 log.warning('Unable to detect git version org error was:%r' % stderr)
726 log.warning('Unable to detect git version org error was:%r' % stderr)
727 elif to_old_git:
727 elif to_old_git:
728 log.warning('RhodeCode detected git version %s, which is too old '
728 log.warning('RhodeCode detected git version %s, which is too old '
729 'for the system to function properly. Make sure '
729 'for the system to function properly. Make sure '
730 'its version is at least %s' % (ver, req_ver))
730 'its version is at least %s' % (ver, req_ver))
731 return _ver
731 return _ver
732
732
733
733
734 @decorator.decorator
734 @decorator.decorator
735 def jsonify(func, *args, **kwargs):
735 def jsonify(func, *args, **kwargs):
736 """Action decorator that formats output for JSON
736 """Action decorator that formats output for JSON
737
737
738 Given a function that will return content, this decorator will turn
738 Given a function that will return content, this decorator will turn
739 the result into JSON, with a content-type of 'application/json' and
739 the result into JSON, with a content-type of 'application/json' and
740 output it.
740 output it.
741
741
742 """
742 """
743 from pylons.decorators.util import get_pylons
743 from pylons.decorators.util import get_pylons
744 from rhodecode.lib.ext_json import json
744 from rhodecode.lib.ext_json import json
745 pylons = get_pylons(args)
745 pylons = get_pylons(args)
746 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
746 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
747 data = func(*args, **kwargs)
747 data = func(*args, **kwargs)
748 if isinstance(data, (list, tuple)):
748 if isinstance(data, (list, tuple)):
749 msg = "JSON responses with Array envelopes are susceptible to " \
749 msg = "JSON responses with Array envelopes are susceptible to " \
750 "cross-site data leak attacks, see " \
750 "cross-site data leak attacks, see " \
751 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
751 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
752 warnings.warn(msg, Warning, 2)
752 warnings.warn(msg, Warning, 2)
753 log.warning(msg)
753 log.warning(msg)
754 log.debug("Returning JSON wrapped action output")
754 log.debug("Returning JSON wrapped action output")
755 return json.dumps(data, encoding='utf-8') No newline at end of file
755 return json.dumps(data, encoding='utf-8')
General Comments 0
You need to be logged in to leave comments. Login now