source: tailor/vcpx/tailor.py @ 129

Revision 129, 15.2 KB checked in by lele@…, 9 years ago (diff)

Remember the abspath of the configfile passed as option

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