source: tailor/vcpx/project.py @ 1178

Revision 1178, 7.3 KB checked in by lele@…, 7 years ago (diff)

Use a common ancestor to recognize tailor exceptions

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Project details
3# :Creato:   gio 04 ago 2005 13:07:31 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
5# :Licenza:  GNU General Public License
6#
7
8"""
9This module implements a higher level of operations, with a Project
10class that knows how to drive the two main activities, bootstrap and
11update, layering on top of DualWorkingDir.
12"""
13
14__docformat__ = 'reStructuredText'
15
16from vcpx import TailorException
17from vcpx.config import ConfigurationError
18from vcpx.statefile import StateFile
19
20
21class UnknownProjectError(TailorException):
22    "Project does not exist"
23
24
25class Project(object):
26    """
27    This class collects the information related to a single project, such
28    as its source and target repositories and state file. All the setup
29    comes from a section in the configuration file (.ini-like format) with
30    the same name as the project.
31
32    Mandatory options are:
33
34    root-directory
35      This is where all the fun will happen: this directory will contain
36      the source and the target working copy, and usually the state and
37      the log file. It support the conventional "~user" to indicate user's
38      home directory.
39
40    subdir
41      This is the subdirectory, relative to the root-directory, where
42      tailor will extract the source working copy. It may be '.' for some
43      backend kinds.
44
45    state-file
46      Name of the state file needed to store tailor last activity.
47
48    source
49      The source repository: a repository name is something like
50      "darcs:somename", that will be loaded from the homonymous section
51      in the configuration.
52
53    target
54      The counterpart of `source`, the repository that will receive the
55      changes coming from there.
56
57    Non mandatory options:
58
59    before-commit
60      This is a function name, or a sequence of function names enclosed
61      by brackets, that will be executed on each changeset just before
62      it get replayed on the target system: this may be used to perform
63      any kind of alteration on the content of the changeset, or to skip
64      some of them.
65
66    after-commit
67      This is a function name, or a sequence of function names enclosed
68      by brackets, that will be executed on each changeset just after
69      the commit on the target system: this may be used for example to
70      create a tag.
71
72    start-revision
73      This identifies from when tailor should start the migration. It can
74      be either ``INITIAL``, to indicate the start of the history, or
75      ``HEAD`` to indicate the current latest changeset, or a backend
76      specific way of indicate a particular revision/tag in the history.
77    """
78
79    def __init__(self, name, config):
80        """
81        Initialize a new instance representing the project `name`.
82        """
83
84        from ConfigParser import Error
85
86        self.loghandler = None
87
88        if not config.has_section(name):
89            raise UnknownProjectError("'%s' is not a known project" % name)
90
91        self.config = config
92        self.name = name
93        self.dwd = None
94        try:
95            self._load()
96        except Error, e:
97            raise ConfigurationError('Invalid configuration: %s' % str(e))
98
99    def __str__(self):
100        return "Project %s at %s:\n\t" % (self.name, self.rootdir) + \
101               "\n\t".join(['%s = %s' % (v, getattr(self, v))
102                            for v in ('source', 'target', 'state_file')])
103
104    def _load(self):
105        """
106        Load relevant information from the configuration.
107        """
108
109        from os import makedirs
110        from os.path import join, exists, expanduser, abspath
111        from logging import getLogger, CRITICAL, DEBUG, FileHandler, \
112             StreamHandler, Formatter
113
114        self.verbose = self.config.get(self.name, 'verbose', False)
115        self.rootdir = abspath(expanduser(self.config.get(self.name,
116                                                          'root-directory',
117                                                          '.')))
118        if not exists(self.rootdir):
119            makedirs(self.rootdir)
120        self.subdir = self.config.get(self.name, 'subdir')
121        if not self.subdir:
122            self.subdir = '.'
123
124        self.logfile = join(self.rootdir, self.name + '.log')
125        self.log = getLogger('tailor.project.%s' % self.name)
126        if self.config.get(self.name, 'debug'):
127            self.log.setLevel(DEBUG)
128        tailorlog = getLogger('tailor')
129        formatter = Formatter(self.config.get(
130            self.name, 'log-format',
131            '%(asctime)s %(levelname)8s: %(message)s', raw=True),
132                              self.config.get(
133            self.name, 'log-datefmt', '%Y-%m-%d %H:%M:%S', raw=True))
134        self.loghandler = FileHandler(self.logfile)
135        self.loghandler.setFormatter(formatter)
136        self.loghandler.setLevel(DEBUG)
137        tailorlog.addHandler(self.loghandler)
138
139        self.source = self.__loadRepository('source')
140        self.target = self.__loadRepository('target')
141        sfpath = join(self.rootdir,
142                      expanduser(self.config.get(self.name,
143                                                 'state-file',
144                                                 self.name + '.state')))
145        self.state_file = StateFile(sfpath, self.config)
146
147        before = self.config.getTuple(self.name, 'before-commit')
148        try:
149            self.before_commit = [self.config.namespace[f] for f in before]
150        except KeyError, e:
151            raise ConfigurationError('Project "%s" before-commit references '
152                                     'unknown function: %s' %
153                                     (self.name, str(e)))
154
155        after = self.config.getTuple(self.name, 'after-commit')
156        try:
157            self.after_commit = [self.config.namespace[f] for f in after]
158        except KeyError, e:
159            raise ConfigurationError('Project "%s" after-commit references '
160                                     'unknown function: %s' %
161                                     (self.name, str(e)))
162
163        if not self.config.get(self.name, 'verbose', False):
164            # Disable console output
165            rootlog = getLogger()
166            rootlog.disabled = True
167            for h in rootlog.handlers:
168                if isinstance(h, StreamHandler):
169                    h.setLevel(CRITICAL)
170
171    def __del__(self):
172        if self.loghandler is not None:
173            from logging import getLogger
174            getLogger('tailor').removeHandler(self.loghandler)
175
176    def __loadRepository(self, which):
177        """
178        Given a repository named 'somekind:somename', return a Repository
179        (or a subclass of it, if 'SomekindRepository' exists) instance
180        that wraps it.
181        """
182
183        from repository import Repository
184
185        repname = self.config.get(self.name, which)
186        return Repository(repname, self, which)
187
188    def exists(self):
189        """
190        Return True if the project exists, False otherwise.
191        """
192
193        return self.state_file.lastAppliedChangeset() is not None
194
195    def workingDir(self):
196        """
197        Return a DualWorkingDir instance, ready to work.
198        """
199
200        from dualwd import DualWorkingDir
201
202        if self.dwd is None:
203            self.dwd = DualWorkingDir(self.source, self.target)
204            self.dwd.setStateFile(self.state_file)
205            self.dwd.setLogfile(self.logfile)
206        return self.dwd
Note: See TracBrowser for help on using the repository browser.