Edgewall Software

root/trunk/trac/env.py

Revision 9156, 29.5 KB checked in by rblank, 4 days ago (diff)

versioncontrol: Use RepositoryManager.get_repository() consistently instead of Environment.get_repository(), and updated the documentation of the latter to emphasize the standard way of retrieving a repository.

  • Property svn:eol-style set to native
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2003-2009 Edgewall Software
4# Copyright (C) 2003-2007 Jonas Borgström <jonas@edgewall.com>
5# All rights reserved.
6#
7# This software is licensed as described in the file COPYING, which
8# you should have received as part of this distribution. The terms
9# are also available at http://trac.edgewall.org/wiki/TracLicense.
10#
11# This software consists of voluntary contributions made by many
12# individuals. For the exact contribution history, see the revision
13# history and logs, available at http://trac.edgewall.org/log/.
14#
15# Author: Jonas Borgström <jonas@edgewall.com>
16
17import os.path
18try:
19    import threading
20except ImportError:
21    import dummy_threading as threading
22import setuptools
23import sys
24from urlparse import urlsplit
25
26from trac import db_default
27from trac.admin import AdminCommandError, IAdminCommandProvider
28from trac.cache import CacheManager
29from trac.config import *
30from trac.core import Component, ComponentManager, implements, Interface, \
31                      ExtensionPoint, TracError
32from trac.db import DatabaseManager
33from trac.util import copytree, create_file, get_pkginfo, makedirs
34from trac.util.compat import any
35from trac.util.text import exception_to_unicode, printerr, printout
36from trac.util.translation import _
37from trac.versioncontrol import RepositoryManager
38from trac.web.href import Href
39
40__all__ = ['Environment', 'IEnvironmentSetupParticipant', 'open_environment']
41
42
43class IEnvironmentSetupParticipant(Interface):
44    """Extension point interface for components that need to participate in the
45    creation and upgrading of Trac environments, for example to create
46    additional database tables."""
47
48    def environment_created():
49        """Called when a new Trac environment is created."""
50
51    def environment_needs_upgrade(db):
52        """Called when Trac checks whether the environment needs to be upgraded.
53       
54        Should return `True` if this participant needs an upgrade to be
55        performed, `False` otherwise.
56        """
57
58    def upgrade_environment(db):
59        """Actually perform an environment upgrade.
60       
61        Implementations of this method should not commit any database
62        transactions. This is done implicitly after all participants have
63        performed the upgrades they need without an error being raised.
64        """
65
66
67class Environment(Component, ComponentManager):
68    """Trac environment manager.
69
70    Trac stores project information in a Trac environment. It consists of a
71    directory structure containing among other things:
72     * a configuration file
73     * an SQLite database (stores tickets, wiki pages...)
74     * project-specific templates and plugins
75     * wiki and ticket attachments
76    """
77    setup_participants = ExtensionPoint(IEnvironmentSetupParticipant)
78
79    shared_plugins_dir = PathOption('inherit', 'plugins_dir', '',
80        """Path of the directory containing additional plugins.
81       
82        Plugins in that directory are loaded in addition to those in the
83        directory of the environment `plugins`, with this one taking
84        precedence.
85       
86        (''since 0.11'')""")
87
88    base_url = Option('trac', 'base_url', '',
89        """Reference URL for the Trac deployment.
90       
91        This is the base URL that will be used when producing documents that
92        will be used outside of the web browsing context, like for example
93        when inserting URLs pointing to Trac resources in notification
94        e-mails.""")
95
96    base_url_for_redirect = BoolOption('trac', 'use_base_url_for_redirect',
97            False, 
98        """Optionally use `[trac] base_url` for redirects.
99       
100        In some configurations, usually involving running Trac behind a HTTP
101        proxy, Trac can't automatically reconstruct the URL that is used to
102        access it. You may need to use this option to force Trac to use the
103        `base_url` setting also for redirects. This introduces the obvious
104        limitation that this environment will only be usable when accessible
105        from that URL, as redirects are frequently used. ''(since 0.10.5)''""")
106
107    secure_cookies = BoolOption('trac', 'secure_cookies', False,
108        """Restrict cookies to HTTPS connections.
109       
110        When true, set the `secure` flag on all cookies so that they are
111        only sent to the server on HTTPS connections. Use this if your Trac
112        instance is only accessible through HTTPS. (''since 0.11.2'')""")
113
114    project_name = Option('project', 'name', 'My Project',
115        """Name of the project.""")
116
117    project_description = Option('project', 'descr', 'My example project',
118        """Short description of the project.""")
119
120    project_url = Option('project', 'url', '',
121        """URL of the main project web site, usually the website in which
122        the `base_url` resides.""")
123
124    project_admin = Option('project', 'admin', '',
125        """E-Mail address of the project's administrator.""")
126
127    project_admin_trac_url = Option('project', 'admin_trac_url', '.',
128        """Base URL of a Trac instance where errors in this Trac should be
129        reported.
130       
131        This can be an absolute or relative URL, or '.' to reference this
132        Trac instance. An empty value will disable the reporting buttons.
133        (''since 0.11.3'')""")
134
135    project_footer = Option('project', 'footer',
136                            'Visit the Trac open source project at<br />'
137                            '<a href="http://trac.edgewall.org/">'
138                            'http://trac.edgewall.org/</a>',
139        """Page footer text (right-aligned).""")
140
141    project_icon = Option('project', 'icon', 'common/trac.ico',
142        """URL of the icon of the project.""")
143
144    log_type = Option('logging', 'log_type', 'none',
145        """Logging facility to use.
146       
147        Should be one of (`none`, `file`, `stderr`, `syslog`, `winlog`).""")
148
149    log_file = Option('logging', 'log_file', 'trac.log',
150        """If `log_type` is `file`, this should be a path to the log-file.""")
151
152    log_level = Option('logging', 'log_level', 'DEBUG',
153        """Level of verbosity in log.
154       
155        Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""")
156
157    log_format = Option('logging', 'log_format', None,
158        """Custom logging format.
159
160        If nothing is set, the following will be used:
161       
162        Trac[$(module)s] $(levelname)s: $(message)s
163
164        In addition to regular key names supported by the Python logger library
165        library (see http://docs.python.org/lib/node422.html), one could use:
166         - $(path)s     the path for the current environment
167         - $(basename)s the last path component of the current environment
168         - $(project)s  the project name
169
170        Note the usage of `$(...)s` instead of `%(...)s` as the latter form
171        would be interpreted by the ConfigParser itself.
172
173        Example:
174        `($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s`
175
176        ''(since 0.10.5)''""")
177
178    def __init__(self, path, create=False, options=[]):
179        """Initialize the Trac environment.
180       
181        @param path:   the absolute path to the Trac environment
182        @param create: if `True`, the environment is created and populated with
183                       default data; otherwise, the environment is expected to
184                       already exist.
185        @param options: A list of `(section, name, value)` tuples that define
186                        configuration options
187        """
188        ComponentManager.__init__(self)
189
190        self.path = path
191        self.setup_config(load_defaults=create)
192        self.setup_log()
193
194        from trac import core, __version__ as VERSION
195        trac_version = get_pkginfo(core).get('version', VERSION)
196        self.systeminfo = [
197            ('Trac', trac_version),
198            ('Python', sys.version),
199            ('setuptools', setuptools.__version__),
200        ]
201        from trac.util.datefmt import pytz
202        if pytz is not None:
203            self.systeminfo.append(('pytz', pytz.__version__))
204        self.log.info('-' * 32 + ' environment startup [Trac %s] ' + '-' * 32,
205                      trac_version)
206        self._href = self._abs_href = None
207
208        from trac.loader import load_components
209        plugins_dir = self.shared_plugins_dir
210        load_components(self, plugins_dir and (plugins_dir,))
211
212        if create:
213            self.create(options)
214        else:
215            self.verify()
216
217        if create:
218            for setup_participant in self.setup_participants:
219                setup_participant.environment_created()
220
221    def component_activated(self, component):
222        """Initialize additional member variables for components.
223       
224        Every component activated through the `Environment` object gets three
225        member variables: `env` (the environment object), `config` (the
226        environment configuration) and `log` (a logger object)."""
227        component.env = self
228        component.config = self.config
229        component.log = self.log
230
231    def _component_name(self, name_or_class):
232        name = name_or_class
233        if not isinstance(name_or_class, basestring):
234            name = name_or_class.__module__ + '.' + name_or_class.__name__
235        return name.lower()
236
237    @property
238    def _component_rules(self):
239        try:
240            return self._rules
241        except AttributeError:
242            self._rules = {}
243            for name, value in self.config.options('components'):
244                if name.endswith('.*'):
245                    name = name[:-2]
246                self._rules[name.lower()] = value.lower() in ('enabled', 'on')
247            return self._rules
248       
249    def is_component_enabled(self, cls):
250        """Implemented to only allow activation of components that are not
251        disabled in the configuration.
252       
253        This is called by the `ComponentManager` base class when a component is
254        about to be activated. If this method returns `False`, the component
255        does not get activated. If it returns `None`, the component only gets
256        activated if it is located in the `plugins` directory of the
257        enironment.
258        """
259        component_name = self._component_name(cls)
260
261        # Disable the pre-0.11 WebAdmin plugin
262        # Please note that there's no recommendation to uninstall the
263        # plugin because doing so would obviously break the backwards
264        # compatibility that the new integration administration
265        # interface tries to provide for old WebAdmin extensions
266        if component_name.startswith('webadmin.'):
267            self.log.info('The legacy TracWebAdmin plugin has been '
268                          'automatically disabled, and the integrated '
269                          'administration interface will be used '
270                          'instead.')
271            return False
272       
273        rules = self._component_rules
274        cname = component_name
275        while cname:
276            enabled = rules.get(cname)
277            if enabled is not None:
278                return enabled
279            idx = cname.rfind('.')
280            if idx < 0:
281                break
282            cname = cname[:idx]
283
284        # By default, all components in the trac package are enabled
285        return component_name.startswith('trac.') or None
286
287    def enable_component(self, cls):
288        """Enable a component or module."""
289        self._component_rules[self._component_name(cls)] = True
290
291    def verify(self):
292        """Verify that the provided path points to a valid Trac environment
293        directory."""
294        fd = open(os.path.join(self.path, 'VERSION'), 'r')
295        try:
296            assert fd.read(26) == 'Trac Environment Version 1'
297        finally:
298            fd.close()
299
300    def get_db_cnx(self):
301        """Return a database connection from the connection pool."""
302        return DatabaseManager(self).get_connection()
303
304    def shutdown(self, tid=None, except_logging=False):
305        """Close the environment."""
306        RepositoryManager(self).shutdown(tid)
307        DatabaseManager(self).shutdown(tid)
308        if tid is None and not except_logging and \
309                hasattr(self.log, '_trac_handler'):
310            hdlr = self.log._trac_handler
311            self.log.removeHandler(hdlr)
312            hdlr.flush()
313            hdlr.close()
314            del self.log._trac_handler
315
316    def get_repository(self, reponame=None, authname=None):
317        """Return the version control repository with the given name, or the
318        default repository if `None`.
319       
320        The standard way of retrieving repositories is to use the methods
321        of `RepositoryManager`. This method is retained here for backward
322        compatibility.
323       
324        @param reponame: the name of the repository
325        @param authname: the user name for authorization (not used anymore,
326                         left here for compatibility with 0.11)
327        """
328        return RepositoryManager(self).get_repository(reponame)
329
330    def create(self, options=[]):
331        """Create the basic directory structure of the environment, initialize
332        the database and populate the configuration file with default values.
333
334        If options contains ('inherit', 'file'), default values will not be
335        loaded; they are expected to be provided by that file or other options.
336        """
337        # Create the directory structure
338        if not os.path.exists(self.path):
339            os.mkdir(self.path)
340        os.mkdir(self.get_log_dir())
341        os.mkdir(self.get_htdocs_dir())
342        os.mkdir(os.path.join(self.path, 'plugins'))
343
344        # Create a few files
345        create_file(os.path.join(self.path, 'VERSION'),
346                    'Trac Environment Version 1\n')
347        create_file(os.path.join(self.path, 'README'),
348                    'This directory contains a Trac environment.\n'
349                    'Visit http://trac.edgewall.org/ for more information.\n')
350
351        # Setup the default configuration
352        os.mkdir(os.path.join(self.path, 'conf'))
353        create_file(os.path.join(self.path, 'conf', 'trac.ini'))
354        create_file(os.path.join(self.path, 'conf', 'trac.ini.sample'))
355        skip_defaults = any((section, option) == ('inherit', 'file')
356                            for section, option, value in options)
357        self.setup_config(load_defaults=not skip_defaults)
358        for section, name, value in options:
359            self.config.set(section, name, value)
360        self.config.save()
361        # Full reload to get 'inherit' working
362        self.config.parse_if_needed(force=True)
363
364        # Create the database
365        DatabaseManager(self).init_db()
366
367    def get_version(self, db=None, initial=False):
368        """Return the current version of the database.
369        If the optional argument `initial` is set to `True`, the version
370        of the database used at the time of creation will be returned.
371
372        In practice, for database created before 0.11, this will return `False`
373        which is "older" than any db version number.
374
375        :since 0.11:
376        """
377        if not db:
378            db = self.get_db_cnx()
379        cursor = db.cursor()
380        cursor.execute("SELECT value FROM system "
381                       "WHERE name='%sdatabase_version'" %
382                       (initial and 'initial_' or ''))
383        row = cursor.fetchone()
384        return row and int(row[0])
385
386    def setup_config(self, load_defaults=False):
387        """Load the configuration file."""
388        self.config = Configuration(os.path.join(self.path, 'conf',
389                                                 'trac.ini'))
390        if load_defaults:
391            for section, default_options in self.config.defaults(self).items():
392                for name, value in default_options.items():
393                    if any(parent[section].contains(name, defaults=False)
394                           for parent in self.config.parents):
395                        value = None
396                    self.config.set(section, name, value)
397
398    def get_templates_dir(self):
399        """Return absolute path to the templates directory."""
400        return os.path.join(self.path, 'templates')
401
402    def get_htdocs_dir(self):
403        """Return absolute path to the htdocs directory."""
404        return os.path.join(self.path, 'htdocs')
405
406    def get_log_dir(self):
407        """Return absolute path to the log directory."""
408        return os.path.join(self.path, 'log')
409
410    def setup_log(self):
411        """Initialize the logging sub-system."""
412        from trac.log import logger_factory
413        logtype = self.log_type
414        logfile = self.log_file
415        if logtype == 'file' and not os.path.isabs(logfile):
416            logfile = os.path.join(self.get_log_dir(), logfile)
417        format = self.log_format
418        if format:
419            format = format.replace('$(', '%(') \
420                     .replace('%(path)s', self.path) \
421                     .replace('%(basename)s', os.path.basename(self.path)) \
422                     .replace('%(project)s', self.project_name)
423        self.log = logger_factory(logtype, logfile, self.log_level, self.path,
424                                  format=format)
425
426    def get_known_users(self, cnx=None):
427        """Generator that yields information about all known users, i.e. users
428        that have logged in to this Trac environment and possibly set their name
429        and email.
430
431        This function generates one tuple for every user, of the form
432        (username, name, email) ordered alpha-numerically by username.
433
434        @param cnx: the database connection; if ommitted, a new connection is
435                    retrieved
436        """
437        if not cnx:
438            cnx = self.get_db_cnx()
439        cursor = cnx.cursor()
440        cursor.execute("SELECT DISTINCT s.sid, n.value, e.value "
441                       "FROM session AS s "
442                       " LEFT JOIN session_attribute AS n ON (n.sid=s.sid "
443                       "  and n.authenticated=1 AND n.name = 'name') "
444                       " LEFT JOIN session_attribute AS e ON (e.sid=s.sid "
445                       "  AND e.authenticated=1 AND e.name = 'email') "
446                       "WHERE s.authenticated=1 ORDER BY s.sid")
447        for username, name, email in cursor:
448            yield username, name, email
449
450    def backup(self, dest=None):
451        """Simple SQLite-specific backup of the database.
452
453        @param dest: Destination file; if not specified, the backup is stored in
454                     a file called db_name.trac_version.bak
455        """
456        return DatabaseManager(self).backup(dest)
457
458    def needs_upgrade(self):
459        """Return whether the environment needs to be upgraded."""
460        db = self.get_db_cnx()
461        for participant in self.setup_participants:
462            if participant.environment_needs_upgrade(db):
463                self.log.warning('Component %s requires environment upgrade',
464                                 participant)
465                return True
466        return False
467
468    def upgrade(self, backup=False, backup_dest=None):
469        """Upgrade database.
470       
471        Each db version should have its own upgrade module, named
472        upgrades/dbN.py, where 'N' is the version number (int).
473
474        @param backup: whether or not to backup before upgrading
475        @param backup_dest: name of the backup file
476        @return: whether the upgrade was performed
477        """
478        db = self.get_db_cnx()
479
480        upgraders = []
481        for participant in self.setup_participants:
482            if participant.environment_needs_upgrade(db):
483                upgraders.append(participant)
484        if not upgraders:
485            return False
486
487        if backup:
488            self.backup(backup_dest)
489        for participant in upgraders:
490            participant.upgrade_environment(db)
491        db.commit()
492
493        # Database schema may have changed, so close all connections
494        self.shutdown(except_logging=True)
495
496        return True
497
498    def _get_href(self):
499        if not self._href:
500            self._href = Href(urlsplit(self.abs_href.base)[2])
501        return self._href
502    href = property(_get_href, 'The application root path')
503
504    def _get_abs_href(self):
505        if not self._abs_href:
506            if not self.base_url:
507                self.log.warn('base_url option not set in configuration, '
508                              'generated links may be incorrect')
509                self._abs_href = Href('')
510            else:
511                self._abs_href = Href(self.base_url)
512        return self._abs_href
513    abs_href = property(_get_abs_href, 'The application URL')
514
515
516class EnvironmentSetup(Component):
517    implements(IEnvironmentSetupParticipant)
518
519    # IEnvironmentSetupParticipant methods
520
521    def environment_created(self):
522        """Insert default data into the database."""
523        db = self.env.get_db_cnx()
524        cursor = db.cursor()
525        for table, cols, vals in db_default.get_data(db):
526            cursor.executemany("INSERT INTO %s (%s) VALUES (%s)" % (table,
527                               ','.join(cols), ','.join(['%s' for c in cols])),
528                               vals)
529        db.commit()
530        self._update_sample_config()
531
532    def environment_needs_upgrade(self, db):
533        dbver = self.env.get_version(db)
534        if dbver == db_default.db_version:
535            return False
536        elif dbver > db_default.db_version:
537            raise TracError(_('Database newer than Trac version'))
538        self.log.info("Trac database schema version is %d, should be %d",
539                      dbver, db_default.db_version)
540        return True
541
542    def upgrade_environment(self, db):
543        cursor = db.cursor()
544        dbver = self.env.get_version()
545        for i in range(dbver + 1, db_default.db_version + 1):
546            name  = 'db%i' % i
547            try:
548                upgrades = __import__('upgrades', globals(), locals(), [name])
549                script = getattr(upgrades, name)
550            except AttributeError:
551                raise TracError(_('No upgrade module for version %(num)i '
552                                  '(%(version)s.py)', num=i, version=name))
553            script.do_upgrade(self.env, i, cursor)
554        cursor.execute("UPDATE system SET value=%s WHERE "
555                       "name='database_version'", (db_default.db_version,))
556        self.log.info('Upgraded database version from %d to %d',
557                      dbver, db_default.db_version)
558        self._update_sample_config()
559
560    # Internal methods
561
562    def _update_sample_config(self):
563        filename = os.path.join(self.env.path, 'conf', 'trac.ini.sample')
564        if not os.path.isfile(filename):
565            return
566        config = Configuration(filename)
567        for section, default_options in config.defaults().iteritems():
568            for name, value in default_options.iteritems():
569                config.set(section, name, value)
570        try:
571            config.save()
572            self.log.info('Wrote sample configuration file with the new '
573                          'settings and their default values: %s',
574                          filename)
575        except IOError, e:
576            self.log.warn('Couldn\'t write sample configuration file (%s)', e,
577                          exc_info=True)
578
579
580env_cache = {}
581env_cache_lock = threading.Lock()
582
583def open_environment(env_path=None, use_cache=False):
584    """Open an existing environment object, and verify that the database is up
585    to date.
586
587    @param env_path: absolute path to the environment directory; if ommitted,
588                     the value of the `TRAC_ENV` environment variable is used
589    @param use_cache: whether the environment should be cached for subsequent
590                      invocations of this function
591    @return: the `Environment` object
592    """
593    if not env_path:
594        env_path = os.getenv('TRAC_ENV')
595    if not env_path:
596        raise TracError(_('Missing environment variable "TRAC_ENV". '
597                          'Trac requires this variable to point to a valid '
598                          'Trac environment.'))
599
600    env_path = os.path.normcase(os.path.normpath(env_path))
601    if use_cache:
602        env_cache_lock.acquire()
603        try:
604            env = env_cache.get(env_path)
605            if env and env.config.parse_if_needed():
606                # The environment configuration has changed, so shut it down
607                # and remove it from the cache so that it gets reinitialized
608                env.log.info('Reloading environment due to configuration '
609                             'change')
610                env.shutdown()
611                del env_cache[env_path]
612                env = None
613            if env is None:
614                env = env_cache.setdefault(env_path, open_environment(env_path))
615            else:
616                CacheManager(env).reset_metadata()
617        finally:
618            env_cache_lock.release()
619    else:
620        env = Environment(env_path)
621        needs_upgrade = False
622        try:
623            needs_upgrade = env.needs_upgrade()
624        except Exception, e: # e.g. no database connection
625            env.log.error("Exception caught while checking for upgrade: %s",
626                          exception_to_unicode(e, traceback=True))
627        if needs_upgrade:
628            raise TracError(_('The Trac Environment needs to be upgraded.\n\n'
629                              'Run "trac-admin %(path)s upgrade"',
630                              path=env_path))
631
632    return env
633
634
635class EnvironmentAdmin(Component):
636    """trac-admin command provider for environment administration."""
637   
638    implements(IAdminCommandProvider)
639   
640    # IAdminCommandProvider methods
641   
642    def get_admin_commands(self):
643        yield ('deploy', '<directory>',
644               'Extract static resources from Trac and all plugins',
645               None, self._do_deploy)
646        yield ('hotcopy', '<backupdir>',
647               'Make a hot backup copy of an environment',
648               None, self._do_hotcopy)
649        yield ('upgrade', '',
650               'Upgrade database to current version',
651               None, self._do_upgrade)
652   
653    def _do_deploy(self, dest):
654        target = os.path.normpath(dest)
655        chrome_target = os.path.join(target, 'htdocs')
656        script_target = os.path.join(target, 'cgi-bin')
657
658        # Copy static content
659        makedirs(target, overwrite=True)
660        makedirs(chrome_target, overwrite=True)
661        from trac.web.chrome import Chrome
662        printout(_("Copying resources from:"))
663        for provider in Chrome(self.env).template_providers:
664            paths = list(provider.get_htdocs_dirs())
665            if not len(paths):
666                continue
667            printout(%s.%s' % (provider.__module__, 
668                                  provider.__class__.__name__))
669            for key, root in paths:
670                source = os.path.normpath(root)
671                printout('   ', source)
672                if os.path.exists(source):
673                    dest = os.path.join(chrome_target, key)
674                    copytree(source, dest, overwrite=True)
675
676        # Create and copy scripts
677        makedirs(script_target, overwrite=True)
678        printout(_("Creating scripts."))
679        data = {'env': self.env, 'executable': sys.executable}
680        for script in ('cgi', 'fcgi', 'wsgi'):
681            dest = os.path.join(script_target, 'trac.' + script)
682            template = Chrome(self.env).load_template('deploy_trac.' + script,
683                                                      'text')
684            stream = template.generate(**data)
685            out = os.fdopen(os.open(dest, os.O_CREAT | os.O_WRONLY), 'w')
686            try:
687                stream.render('text', out=out)
688            finally:
689                out.close()
690   
691    def _do_hotcopy(self, dest):
692        if os.path.exists(dest):
693            raise TracError(_("hotcopy can't overwrite existing '%(dest)s'",
694                              dest=dest))
695        import shutil
696
697        # Bogus statement to lock the database while copying files
698        cnx = self.env.get_db_cnx()
699        cursor = cnx.cursor()
700        cursor.execute("UPDATE system SET name=NULL WHERE name IS NULL")
701
702        try:
703            printout(_('Hotcopying %(src)s to %(dst)s ...', 
704                       src=self.env.path, dst=dest))
705            db_str = self.env.config.get('trac', 'database')
706            prefix, db_path = db_str.split(':', 1)
707            if prefix == 'sqlite':
708                # don't copy the journal (also, this would fail on Windows)
709                db = os.path.join(self.env.path, os.path.normpath(db_path))
710                skip = [db + '-journal', db + '-stmtjrnl']
711            else:
712                skip = []
713            try:
714                copytree(self.env.path, dest, symlinks=1, skip=skip)
715                retval = 0
716            except shutil.Error, e:
717                retval = 1
718                printerr(_('The following errors happened while copying '
719                           'the environment:'))
720                for (src, dst, err) in e.args[0]:
721                    if src in err:
722                        printerr(%s' % err)
723                    else:
724                        printerr(%s: '%s'" % (err, src))
725        finally:
726            # Unlock database
727            cnx.rollback()
728
729        printout(_("Hotcopy done."))
730        return retval
731   
732    def _do_upgrade(self, no_backup=None):
733        if no_backup not in (None, '-b', '--no-backup'):
734            raise AdminCommandError(_("Invalid arguments"), show_usage=True)
735       
736        if not self.env.needs_upgrade():
737            printout(_("Database is up to date, no upgrade necessary."))
738            return
739
740        try:
741            self.env.upgrade(backup=no_backup is None)
742        except TracError, e:
743            msg = unicode(e)
744            if 'backup' in msg.lower():
745                raise TracError(_("Backup failed with '%(msg)s'.\nUse "
746                                  "'--no-backup' to upgrade without doing a "
747                                  "backup.", msg=msg))
748            else:
749                raise
750        printout(_("Upgrade done."))
Note: See TracBrowser for help on using the repository browser.