source: tailor/vcpx/project.py @ 542

Revision 542, 7.8 KB checked in by lele@…, 8 years ago (diff)

Store the name of the logfile

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
16class StateFile(object):
17    """
18    State file that stores current revision and pending changesets.
19    """
20    def __init__(self, fname, config):
21        self.filename = fname
22
23    def __str__(self):
24        return self.filename
25
26    def load(self):
27        """
28        Read the source revision and pending changesets from the state file.
29        """
30
31        from cPickle import load
32
33        try:
34            sf = open(self.filename)
35            revision, changesets = load(sf)
36            sf.close()
37        except IOError:
38            revision = None
39            changesets = None
40
41        return revision, changesets
42
43    def write(self, revision, changesets):
44        """
45        Write current source revision and pending changesets in the state file.
46        """
47
48        from cPickle import dump
49
50        sf = open(self.filename, 'w')
51        dump((revision, changesets), sf)
52        sf.close()
53
54
55class Project(object):
56    """
57    This class collects the information related to a single project, such
58    as its source and target repositories and state file.
59    """
60
61    def __init__(self, name, config):
62        self.config = config
63        self.name = name
64        self.dwd = None
65        self._load()
66
67    def __str__(self):
68        return "Project %s at %s:\n\t" % (self.name, self.rootdir) + \
69               "\n\t".join(['%s = %s' % (v, getattr(self, v))
70                            for v in ('source', 'target', 'state_file')])
71
72    def _load(self):
73        """
74        Load relevant information from the configuration.
75        """
76
77        from os import getcwd, makedirs
78        from os.path import join, exists, expanduser
79        import logging
80
81        self.rootdir = self.config.get(self.name, 'root-directory', getcwd())
82        if not exists(self.rootdir):
83            makedirs(self.rootdir)
84        self.subdir = self.config.get(self.name, 'subdir')
85        if not self.subdir:
86            self.subdir = '.'
87
88        self.source = self.__loadRepository('source')
89        self.target = self.__loadRepository('target')
90        sfpath = join(self.rootdir,
91                      expanduser(self.config.get(self.name, 'state-file')))
92        self.state_file = StateFile(sfpath, self.config)
93
94        before = self.config.getTuple(self.name, 'before-commit')
95        try:
96            self.before_commit = [self.config.namespace[f] for f in before]
97        except KeyError, e:
98            raise ConfigurationError('Project %s before-commit references '
99                                     'unknown function: %s' %
100                                     (self.name, str(e)))
101
102        after = self.config.getTuple(self.name, 'after-commit')
103        try:
104            self.after_commit = [self.config.namespace[f] for f in after]
105        except KeyError, e:
106            raise ConfigurationError('Project %s after-commit references '
107                                     'unknown function: %s' %
108                                     (self.name, str(e)))
109
110        self.verbose = self.config.get(self.name, 'verbose', False)
111        self.single_commit = self.config.get(self.name, 'single-commit', False)
112        self.logger = logging.getLogger('tailor.%s' % self.name)
113        self.logfile = join(self.rootdir,
114                            expanduser(self.config.get(self.name, 'logfile',
115                                                       'tailor.log')))
116        hdlr = logging.FileHandler(self.logfile)
117        formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
118        hdlr.setFormatter(formatter)
119        self.logger.addHandler(hdlr)
120        self.logger.setLevel(logging.INFO)
121
122    def log_info(self, what):
123        """
124        Print some info on the log and, in verbose mode, to stdout as well.
125        """
126
127        if self.logger:
128            self.logger.info(what)
129
130        if self.verbose:
131            print what
132
133    def log_error(self, what, exc=False):
134        """
135        Print an error message, possibly with an exception traceback,
136        to the log and to stdout as well.
137        """
138
139        if self.logger:
140            if exc:
141                self.logger.exception(what)
142            else:
143                self.logger.error(what)
144
145        print "Error:", what,
146        if exc:
147            from sys import exc_info
148
149            ei = exc_info()
150            print ' -- Exception %s: %s' % ei[0:2]
151        else:
152            print
153
154    def __loadRepository(self, which):
155        """
156        Given a repository named 'somekind:somename', return a Repository
157        (or a subclass of it, if 'SomekindRepository' exists) instance
158        that wraps it.
159        """
160
161        import repository
162
163        repname = self.config.get(self.name, which)
164        kind = repname[:repname.index(':')]
165        klassname = kind.capitalize() + 'Repository'
166        try:
167            klass = getattr(repository, klassname)
168        except AttributeError:
169            klass = repository.Repository
170        return klass(repname, kind, self, which)
171
172    def workingDir(self):
173        """
174        Return a DualWorkingDir instance, ready to work.
175        """
176
177        from dualwd import DualWorkingDir
178
179        if self.dwd is None:
180            self.dwd = DualWorkingDir(self.source, self.target)
181            self.dwd.setStateFile(self.state_file)
182            self.dwd.setLogfile(self.logfile)
183        return self.dwd
184
185    def prepareWorkingDirectory(self):
186        """
187        Prepare the working directory before the bootstrap.
188        """
189
190        dwd = self.workingDir()
191        dwd.prepareWorkingDirectory(self.source)
192
193    def checkoutUpstreamRevision(self):
194        """
195        Checkout a working copy from the upstream repository and import
196        it in the target system.
197        """
198
199        dwd = self.workingDir()
200        revision = self.config.get(self.name, 'start-revision', 'INITIAL')
201        actual = dwd.checkoutUpstreamRevision(revision)
202        dwd.initializeNewWorkingDir(self.source, actual, revision=='INITIAL')
203
204    def _applyable(self, changeset):
205        """
206        Print the changeset being applied.
207        """
208
209        if self.verbose:
210            print "Changeset %s:" % changeset.revision
211            try:
212                print changeset.log
213            except UnicodeEncodeError:
214                print ">>> Non-printable changelog <<<"
215
216        return True
217
218    def _applied(self, changeset):
219        """
220        Save current status.
221        """
222
223        if self.verbose:
224            print
225
226    def applyPendingChangesets(self):
227        """
228        Apply pending changesets, eventually fetching latest from upstream.
229        """
230
231        dwd = self.workingDir()
232        try:
233            pendings = dwd.getPendingChangesets()
234        except KeyboardInterrupt:
235            self.log_info("Leaving '%s' unchanged, stopped by user" % self.name)
236            return
237        except:
238            self.log_error("Unable to get changes for '%s'" % self.name, True)
239            raise
240
241        nchanges = len(pendings)
242        if nchanges:
243            if self.verbose:
244                print "Applying %d upstream changesets" % nchanges
245
246            try:
247                last, conflicts = dwd.applyPendingChangesets(
248                    applyable=self._applyable, applied=self._applied)
249            except:
250                self.log_error('Upstream change application failed', True)
251                raise
252
253            if last:
254                self.log_info("Update completed, now at revision '%s'" %
255                              last.revision)
256        else:
257            self.log_info("Update completed with no upstream changes")
Note: See TracBrowser for help on using the repository browser.