source: tailor/vcpx/tailor.py @ 125

Revision 125, 14.6 KB checked in by lele@…, 9 years ago (diff)

Adjust minor nits in the new TailorConfig?

Line 
1#! /usr/bin/python
2# -*- mode: python; coding: utf-8 -*-
3# :Progetto: vcpx -- Frontend capabilities
4# :Creato:   dom 04 lug 2004 00:40:54 CEST
5# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
6#
7
8"""
9Implement the basic capabilities of the frontend.
10
11This implementation stores the relevant project information, needed to
12keep the whole thing going on, such as the last synced revision, in a
13unversioned file named `tailor.info` at the root.
14"""
15
16__docformat__ = 'reStructuredText'
17
18from dualwd import DualWorkingDir
19
20STATUS_FILENAME = 'tailor.info'
21LOG_FILENAME = 'tailor.log'
22
23class TailorConfig(object):
24    def __init__(self, options):
25        self.options = options
26       
27    def __call__(self, args):
28        from os.path import abspath
29       
30        self.__load()
31
32        if len(args) == 0 and self.options.update:
33            args = self.config.keys()
34
35        for a in args:
36            root = abspath(a)
37           
38            tailored = TailorizedProject(root, self.options.verbose, self)
39           
40            if self.options.bootstrap:               
41                tailored.bootstrap(self.options.source_kind,
42                                   self.options.target_kind,
43                                   self.options.repository,
44                                   self.options.module,
45                                   self.options.revision)
46            elif self.options.migrate:
47                tailored.migrateConfiguration()
48            elif self.options.update:
49                tailored.update(self.options.single_commit,
50                                self.options.concatenate_logs)
51
52        self.__save()
53       
54    def __save(self):
55        from pprint import pprint
56
57        configfile = open(self.options.configfile, 'w')
58        pprint(self.config, configfile)
59        configfile.close()
60
61    def __load(self):
62        from os.path import exists
63
64        if exists(self.options.configfile):
65            configfile = open(self.options.configfile)
66            self.config = eval(configfile.read())
67            configfile.close()
68        else:
69            self.config = {}
70           
71    def loadProject(self, project):
72        info = self.config.get(project.root)
73
74        if info:
75            project.source_kind = info['source_kind']
76            project.target_kind = info['target_kind']
77            project.module = info['module']
78            project.upstream_repos = info['upstream_repos']
79            project.upstream_revision = info['upstream_revision']
80
81        return info
82       
83    def saveProject(self, project):
84        self.config[project.root] = { 
85            'source_kind': project.source_kind,
86            'target_kind': project.target_kind,
87            'module': project.module,
88            'upstream_repos': project.upstream_repos,
89            'upstream_revision': project.upstream_revision,
90            }
91
92   
93class TailorizedProject(object):
94    """
95    A TailorizedProject has two main capabilities: it may be bootstrapped
96    from an upstream repository or brought in sync with current upstream
97    revision.
98    """
99   
100    def __init__(self, root, verbose=False, config=None):
101        import logging
102        from os import makedirs
103        from os.path import join, exists, split
104
105        self.root = root
106        if not exists(root):
107            makedirs(root)
108
109        self.verbose = verbose
110        self.logger = logging.getLogger('tailor.%s' % split(root)[1])
111        hdlr = logging.FileHandler(join(root, LOG_FILENAME))
112        formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
113        hdlr.setFormatter(formatter)
114        self.logger.addHandler(hdlr) 
115        self.logger.setLevel(logging.INFO)
116
117        self.source_kind = self.target_kind = None
118
119        self.config = config
120
121    def migrateConfiguration(self):
122        self.__loadOldStatus()
123        self.__saveStatus()
124       
125    def __saveOldStatus(self):
126        from os.path import join
127
128        statusfilename = join(self.root, STATUS_FILENAME)
129        f = open(statusfilename, 'w')
130        print >>f, self.source_kind
131        print >>f, self.target_kind       
132        print >>f, self.module       
133        print >>f, self.upstream_repos
134        print >>f, self.upstream_revision
135        f.close()
136
137    def __saveStatus(self):
138        """
139        Save relevant project information in a persistent way.
140        """
141
142        if self.config:
143            self.config.saveProject(self)
144        else:
145            self.__saveOldStatus()
146
147    def __loadOldStatus(self):
148        from os.path import join
149
150        statusfilename = join(self.root, STATUS_FILENAME)
151        f = open(statusfilename)
152        (srck, dstk,
153         module, upstream_repos, upstream_revision) = f.readlines()
154        self.source_kind = srck[:-1]
155        self.target_kind = dstk[:-1]
156        self.module = module[:-1]
157        self.upstream_repos = upstream_repos[:-1]
158        self.upstream_revision = upstream_revision[:-1]
159        f.close()
160
161    def __loadStatus(self):
162        """
163        Load relevant project information.
164        """
165
166        if self.config:
167            self.config.loadProject(self)
168        else:
169            self.__loadOldStatus()
170           
171    def bootstrap(self, source_kind, target_kind, repository, module,revision):
172        """
173        Bootstrap a new tailorized module.
174
175        Extract a copy of the `repository` at given `revision` in the `root`
176        directory and initialize a target repository with its content.
177
178        The actual information on the project are stored in a text file.
179        """
180
181        from os.path import split
182       
183        self.logger.info("Bootstrapping '%s'" % (self.root,))
184
185        dwd = DualWorkingDir(source_kind, target_kind)
186        self.logger.info("getting %s revision '%s' of '%s' from '%s'" % (
187            source_kind, revision, module, repository))
188        actual = dwd.checkoutUpstreamRevision(self.root, repository,
189                                              module, revision,
190                                              logger=self.logger)
191
192        # the above machinery checked out a copy under of the wc
193        # in the directory named as the last component of the module's name
194
195        dwd.initializeNewWorkingDir(self.root, repository,
196                                    split(module)[1], actual)
197
198        self.source_kind = source_kind
199        self.target_kind = target_kind
200        self.upstream_repos = repository
201        self.module = module       
202        self.upstream_revision = actual
203
204        self.__saveStatus()
205
206        self.logger.info("Bootstrap completed")
207
208    def applied(self, root, changeset):
209        """
210        Save current status.
211        """
212
213        self.upstream_revision = changeset.revision
214        self.__saveStatus()
215        if self.verbose:
216            print "# Applied changeset %s" % changeset.revision
217            print changeset.log
218
219    def update(self, single_commit, concatenate_logs):
220        """
221        Update an existing tailorized project.
222
223        Fetch the upstream changesets and apply them to the working copy.
224        Use the information stored in the `tailor.info` file to ask just
225        the new changeset since last bootstrap/synchronization.
226        """
227       
228        from os.path import join, split
229       
230        self.__loadStatus()
231
232        proj = join(self.root, split(self.module)[1])
233        self.logger.info("Updating '%s' from revision '%s'" % (
234            self.module, self.upstream_revision))
235
236        if self.verbose:
237            print "\nUpdating '%s' from revision '%s'" % (self.module,
238                                                          self.upstream_revision)
239       
240        dwd = DualWorkingDir(self.source_kind, self.target_kind)
241        changesets = dwd.getUpstreamChangesets(proj, self.upstream_revision)
242
243        nchanges = len(changesets)
244        if nchanges:
245            if self.verbose:
246                print "Applying %d upstream changesets" % nchanges
247               
248            l,c = dwd.applyUpstreamChangesets(proj, changesets,
249                                              applied=self.applied,
250                                              logger=self.logger,
251                                              delayed_commit=single_commit)
252            if l:
253                if single_commit:
254                    dwd.commitDelayedChangesets(proj, concatenate_logs)
255
256                self.logger.info("Update completed, now at revision '%s'" % (
257                    self.upstream_revision,))
258        else:
259            self.logger.info("Update completed with no upstream changes")
260
261
262from optparse import OptionParser, OptionError, OptionGroup, make_option
263
264GENERAL_OPTIONS = [
265    make_option("-D", "--debug", dest="debug",
266                action="store_true", default=False,
267                help="Print each executed command."),
268    make_option("-v", "--verbose", dest="verbose",
269                action="store_true", default=False,
270                help="Be verbose, echoing the changelog of each applied "
271                     "changeset to stdout."),
272    make_option("--configfile", metavar="CONFNAME",
273                help="Centralized storage of projects info.  With this "
274                     "option and no other arguments tailor will update "
275                     "every project found in the config file."),
276    make_option("--migrate-config", dest="migrate",
277                action="store_true", default=False,
278                help="Migrate old configuration to new centralized storage."),
279]   
280
281UPDATE_OPTIONS = [
282    make_option("--update", action="store_true", default=True,
283                help="Update the given repositories, fetching upstream "
284                     "changesets, applying and re-registering each one. "
285                     "This is the default behaviour."),
286    make_option("-S", "--single-commit", action="store_true", default=False,
287                help="Do a single, final commit on the target VC, effectively "
288                     "grouping together all upstream changeset into a single "
289                     "one, from the target VC point of view."),
290    make_option("-C", "--concatenate-logs", action="store_true", default=False,
291                help="With --single-commit, concatenate each changeset "
292                     "message log to the final changelog, instead of just "
293                     "the name of the patch."),
294]
295
296BOOTSTRAP_OPTIONS = [
297    make_option("-b", "--bootstrap", action="store_true", default=False,
298                help="Bootstrap mode, that is the initial copy of the "
299                     "upstream tree, given as an URI (see -R) and maybe "
300                     "a revision (-r).  This overrides --update."),
301    make_option("-s", "--source-kind", dest="source_kind", metavar="VC-KIND",
302                help="Select the backend for the upstream source "
303                     "version control VC-KIND. Default is 'cvs'.",
304                default="cvs"),
305    make_option("-t", "--target-kind", dest="target_kind", metavar="VC-KIND",
306                help="Select VC-KIND as backend for the shadow repository, "
307                     "with 'darcs' as default.",
308                default="darcs"),
309    make_option("-R", "--repository", dest="repository", metavar="REPOS",
310                help="Specify the upstream repository, from where bootstrap "
311                     "will checkout the module.  REPOS syntax depends on "
312                     "the source version control kind."),
313    make_option("-m", "--module", dest="module", metavar="MODULE",
314                help="Specify the module to checkout at bootstrap time."),
315    make_option("-r", "--revision", dest="revision", metavar="REV",
316                help="Specify the revision bootstrap should checkout.  REV "
317                     "must be a valid 'name' for a revision in the upstream "
318                     "version control kind.  For CVS it may be a tag/branch. "
319                     "'HEAD', the default, means the latest version in all "
320                     "backends.",
321                default="HEAD"),
322]
323
324class ExistingProjectError(Exception):
325    """
326    Raised when, in bootstrap mode, the directory for the project is already
327    there.
328    """
329
330class UnknownProjectError(Exception):
331    """
332    Raised when, in normal mode, the directory for the project does not
333    exist.
334    """
335
336class ProjectNotTailored(Exception):
337    """
338    Raised when trying to do something on a project that has not been
339    tailored.
340    """
341   
342def main():
343    """
344    Script entry point.
345
346    Parse the command line options and arguments, and for each
347    specified working copy directory (the current working directory by
348    default) execute the tailorization steps.
349    """
350   
351    from os import getcwd, chdir
352    from os.path import abspath, exists, join, split
353    from shwrap import SystemCommand
354   
355    parser = OptionParser(usage='%prog [options] [project ...]',
356                          option_list=GENERAL_OPTIONS)
357   
358    bsoptions = OptionGroup(parser, "Bootstrap options")
359    bsoptions.add_options(BOOTSTRAP_OPTIONS)
360
361    upoptions = OptionGroup(parser, "Update options")
362    upoptions.add_options(UPDATE_OPTIONS)
363   
364    parser.add_option_group(bsoptions)
365    parser.add_option_group(upoptions)
366   
367    options, args = parser.parse_args()
368   
369    SystemCommand.VERBOSE = options.debug
370
371    base = getcwd()
372       
373    if options.configfile:
374        config = TailorConfig(options)
375
376        config(args)
377    else:
378        # Good (?) old way
379       
380        if len(args) == 0:
381            args.append(base)
382
383        while args:
384            chdir(base)
385
386            proj = args.pop(0)
387            root = abspath(proj)
388
389            if options.bootstrap:
390                if exists(join(root, STATUS_FILENAME)):
391                    raise ExistingProjectError(
392                        "Project %r cannot be bootstrapped twice" % proj)
393
394                if not options.repository:
395                    raise OptionError('Need a repository to bootstrap %r' %
396                                      proj)
397            else:
398                if not exists(proj):
399                    raise UnknownProjectError("Project %r does not exist" %
400                                              proj)
401
402                if not exists(join(root, STATUS_FILENAME)):
403                    raise UnknownProjectError(
404                        "%r is not a tailorized project" % proj)
405
406            tailored = TailorizedProject(root, options.verbose, config)
407
408            if options.bootstrap:
409                tailored.bootstrap(options.source_kind, options.target_kind,
410                                   options.repository,
411                                   options.module,
412                                   options.revision)
413            elif options.update:
414                tailored.update(options.single_commit,
415                                options.concatenate_logs)
416
Note: See TracBrowser for help on using the repository browser.