source: tailor/vcpx/project.py @ 545

Revision 545, 7.7 KB checked in by lele@…, 8 years ago (diff)

Removed unused option

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.logger = logging.getLogger('tailor.%s' % self.name)
112        self.logfile = join(self.rootdir,
113                            expanduser(self.config.get(self.name, 'logfile',
114                                                       'tailor.log')))
115        hdlr = logging.FileHandler(self.logfile)
116        formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
117        hdlr.setFormatter(formatter)
118        self.logger.addHandler(hdlr)
119        self.logger.setLevel(logging.INFO)
120
121    def log_info(self, what):
122        """
123        Print some info on the log and, in verbose mode, to stdout as well.
124        """
125
126        if self.logger:
127            self.logger.info(what)
128
129        if self.verbose:
130            print what
131
132    def log_error(self, what, exc=False):
133        """
134        Print an error message, possibly with an exception traceback,
135        to the log and to stdout as well.
136        """
137
138        if self.logger:
139            if exc:
140                self.logger.exception(what)
141            else:
142                self.logger.error(what)
143
144        print "Error:", what,
145        if exc:
146            from sys import exc_info
147
148            ei = exc_info()
149            print ' -- Exception %s: %s' % ei[0:2]
150        else:
151            print
152
153    def __loadRepository(self, which):
154        """
155        Given a repository named 'somekind:somename', return a Repository
156        (or a subclass of it, if 'SomekindRepository' exists) instance
157        that wraps it.
158        """
159
160        import repository
161
162        repname = self.config.get(self.name, which)
163        kind = repname[:repname.index(':')]
164        klassname = kind.capitalize() + 'Repository'
165        try:
166            klass = getattr(repository, klassname)
167        except AttributeError:
168            klass = repository.Repository
169        return klass(repname, kind, self, which)
170
171    def workingDir(self):
172        """
173        Return a DualWorkingDir instance, ready to work.
174        """
175
176        from dualwd import DualWorkingDir
177
178        if self.dwd is None:
179            self.dwd = DualWorkingDir(self.source, self.target)
180            self.dwd.setStateFile(self.state_file)
181            self.dwd.setLogfile(self.logfile)
182        return self.dwd
183
184    def prepareWorkingDirectory(self):
185        """
186        Prepare the working directory before the bootstrap.
187        """
188
189        dwd = self.workingDir()
190        dwd.prepareWorkingDirectory(self.source)
191
192    def checkoutUpstreamRevision(self):
193        """
194        Checkout a working copy from the upstream repository and import
195        it in the target system.
196        """
197
198        dwd = self.workingDir()
199        revision = self.config.get(self.name, 'start-revision', 'INITIAL')
200        actual = dwd.checkoutUpstreamRevision(revision)
201        dwd.initializeNewWorkingDir(self.source, actual, revision=='INITIAL')
202
203    def _applyable(self, changeset):
204        """
205        Print the changeset being applied.
206        """
207
208        if self.verbose:
209            print "Changeset %s:" % changeset.revision
210            try:
211                print changeset.log
212            except UnicodeEncodeError:
213                print ">>> Non-printable changelog <<<"
214
215        return True
216
217    def _applied(self, changeset):
218        """
219        Save current status.
220        """
221
222        if self.verbose:
223            print
224
225    def applyPendingChangesets(self):
226        """
227        Apply pending changesets, eventually fetching latest from upstream.
228        """
229
230        dwd = self.workingDir()
231        try:
232            pendings = dwd.getPendingChangesets()
233        except KeyboardInterrupt:
234            self.log_info("Leaving '%s' unchanged, stopped by user" % self.name)
235            return
236        except:
237            self.log_error("Unable to get changes for '%s'" % self.name, True)
238            raise
239
240        nchanges = len(pendings)
241        if nchanges:
242            if self.verbose:
243                print "Applying %d upstream changesets" % nchanges
244
245            try:
246                last, conflicts = dwd.applyPendingChangesets(
247                    applyable=self._applyable, applied=self._applied)
248            except:
249                self.log_error('Upstream change application failed', True)
250                raise
251
252            if last:
253                self.log_info("Update completed, now at revision '%s'" %
254                              last.revision)
255        else:
256            self.log_info("Update completed with no upstream changes")
Note: See TracBrowser for help on using the repository browser.