Changeset 485 in tailor


Ignore:
Timestamp:
08/05/05 00:27:29 (8 years ago)
Author:
lele@…
Hash name:
20050804222729-97f81-0e9a149c0c69768a5abb729e7f2aa54e5e06eeea
Message:

Config-geddon
Almost completed the transition to the new configuration system. Now,
with the following example config::

[DEFAULT]
verbose = Yes
projects = tailor

[tailor]
root = /tmp/n9
source = darcs:tailor
target = svn:tailor
refill-changelogs = Yes
state-file = project1.state

[svn:tailor]
repository =  file:///tmp/testtailor
module = /project1

[darcs:tailor]
repository = /home/lele/WiP/cvsync

the command line::

tailor --configfile /tmp/example.config --bootstrap -D -v

created an empty Subversion repository in /tmp/testtailor and imported
there the first patch from the tailor's Darcs repository.

Next, without --bootstrap, it started importing the remaining patches.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • vcpx/tailor.py

    r469 r485  
    1919 
    2020from optparse import OptionParser, OptionGroup, make_option 
    21 from dualwd import DualWorkingDir 
     21from config import Config 
    2222from source import InvocationError 
    2323from session import interactive 
    24 from svn import SvnWorkingDir 
    25  
    26 STATUS_FILENAME = 'tailor.info' 
    27 LOG_FILENAME = 'tailor.log' 
    28  
    29 def relpathto(source, dest): 
     24 
     25class Tailorizer(object): 
    3026    """ 
    31     Compute the relative path needed to point ``source`` from ``dest``. 
    32  
    33     Warning: ``dest`` is assumed to be a directory. 
    34     """ 
    35  
    36     from os.path import abspath, split, commonprefix 
    37  
    38     source = abspath(source) 
    39     dest = abspath(dest) 
    40  
    41     if source.startswith(dest): 
    42         return source[len(dest)+1:] 
    43  
    44     prefix = commonprefix([source, dest]) 
    45  
    46     source = source[len(prefix):] 
    47     dest = dest[len(prefix):] 
    48  
    49     return '../' * len(dest.split('/')) + source 
    50  
    51  
    52 class TailorConfig(object): 
    53     """ 
    54     Configuration of a set of tailorized projects. 
    55  
    56     The configuration is stored in a persistent dictionary keyed on the 
    57     relative path of each project. The information about a single project 
    58     is another dictionary. 
    59     """ 
    60  
    61     def __init__(self, options): 
    62         from os.path import abspath, split 
    63  
    64         self.options = options 
    65         self.configfile = abspath(options.configfile) 
    66         self.basedir = split(self.configfile)[0] 
    67  
    68     def __call__(self, args): 
    69         from os.path import join, exists, split 
    70         from source import ChangesetApplicationFailure 
    71  
    72         self.__load() 
    73  
    74         if len(args) == 0: 
    75             fromconfig = True 
    76             if self.options.bootstrap: 
    77                 f = lambda x: not exists(x) 
    78             else: 
    79                 f = exists 
    80  
    81             args = [p for p in [join(self.basedir, r) 
    82                                 for r in self.config.keys()] if f(p)] 
    83             args.sort() 
    84         else: 
    85             fromconfig = False 
    86  
    87         try: 
    88             for root in args: 
    89                 if self.options.bootstrap: 
    90                     if not (fromconfig or self.options.source_repository): 
    91                         raise InvocationError('Need a repository to bootstrap ' 
    92                                               '%r' % root, '--bootstrap') 
    93                 else: 
    94                     if not self.config.has_key(relpathto(root, self.basedir)): 
    95                         raise UnknownProjectError("Project %r does not exist" % 
    96                                                   root) 
    97  
    98                 tailored = TailorizedProject(root, self.options.verbose, self) 
    99  
    100                 if self.options.bootstrap: 
    101                     if fromconfig: 
    102                         info = self.loadProject(root=root) 
    103                         self.options.source_kind = info['source_kind'] 
    104                         self.options.target_kind = info['target_kind'] 
    105                         self.options.source_repository = info['upstream_repos'] 
    106                         self.options.source_module = info['module'] 
    107                         self.options.subdir = info.get('subdir', 
    108                                                        split(info['module'])[1]) 
    109                         self.options.revision = info['upstream_revision'] 
    110  
    111                     tailored.bootstrap(self.options.source_kind, 
    112                                        self.options.target_kind, 
    113                                        self.options.source_repository, 
    114                                        self.options.source_module, 
    115                                        self.options.revision, 
    116                                        self.options.target_repository, 
    117                                        self.options.target_module, 
    118                                        self.options.subdir) 
    119                 elif self.options.migrate: 
    120                     tailored.migrateConfiguration() 
    121                 elif self.options.update: 
    122                     try: 
    123                         tailored.update(self.options.single_commit, 
    124                                         self.options.concatenate_logs) 
    125                     except ChangesetApplicationFailure, e: 
    126                         print "Skipping '%s' because of errors:" % root, e 
    127         finally: 
    128             self.__save() 
    129  
    130     def __save(self): 
    131         from pprint import pprint 
    132  
    133         configfile = open(self.configfile, 'w') 
    134         pprint(self.config, configfile) 
    135         configfile.close() 
    136  
    137     def __load(self): 
    138         from os.path import exists 
    139  
    140         if exists(self.options.configfile): 
    141             configfile = open(self.configfile) 
    142             self.config = eval(configfile.read()) 
    143             configfile.close() 
    144         else: 
    145             self.config = {} 
    146  
    147     def loadProject(self, project=None, root=None): 
    148         from os.path import split 
    149  
    150         relpath = relpathto(project and project.root or root, self.basedir) 
    151  
    152         info = self.config.get(relpath) 
    153         if info and project: 
    154             project.source_kind = info['source_kind'] 
    155             project.target_kind = info['target_kind'] 
    156             project.upstream_module = info['module'] 
    157             project.subdir = info.get('subdir', 
    158                                       split(project.upstream_module)[1]) 
    159             project.upstream_repos = info['upstream_repos'] 
    160             project.upstream_revision = info['upstream_revision'] 
    161  
    162         return info 
    163  
    164     def saveProject(self, project): 
    165         relpath = relpathto(project.root, self.basedir) 
    166  
    167         self.config[relpath] = { 
    168             'source_kind': project.source_kind, 
    169             'target_kind': project.target_kind, 
    170             'module': project.upstream_module, 
    171             'subdir': project.subdir, 
    172             'upstream_repos': project.upstream_repos, 
    173             'upstream_revision': project.upstream_revision, 
    174             } 
    175  
    176  
    177 class TailorizedProject(object): 
    178     """ 
    179     A TailorizedProject has two main capabilities: it may be bootstrapped 
    180     from an upstream repository or brought in sync with current upstream 
     27    A Tailorizer has two main capabilities: its able to bootstrap a 
     28    new Project, or brought it in sync with its current upstream 
    18129    revision. 
    18230    """ 
    18331 
    184     def __init__(self, root, verbose=False, config=None): 
    185         import logging 
    186         from os import makedirs 
    187         from os.path import join, exists, split 
    188  
    189         self.root = root 
    190         if not exists(root): 
    191             makedirs(root) 
    192  
    193         self.verbose = verbose 
    194         self.logger = logging.getLogger('tailor.%s' % split(root)[1]) 
    195         hdlr = logging.FileHandler(join(root, LOG_FILENAME)) 
    196         formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s') 
    197         hdlr.setFormatter(formatter) 
    198         self.logger.addHandler(hdlr) 
    199         self.logger.setLevel(logging.INFO) 
    200  
    201         self.source_kind = self.target_kind = None 
    202  
    203         self.config = config 
    204  
    205     def migrateConfiguration(self): 
    206         self.__loadOldStatus() 
    207         self.__saveStatus() 
    208  
    209     def __saveOldStatus(self): 
    210         from os.path import join 
    211  
    212         statusfilename = join(self.root, STATUS_FILENAME) 
    213         f = open(statusfilename, 'w') 
    214         print >>f, self.source_kind 
    215         print >>f, self.target_kind 
    216         print >>f, self.upstream_module 
    217         print >>f, self.upstream_repos 
    218         print >>f, self.upstream_revision 
    219         print >>f, self.subdir 
    220         f.close() 
    221  
    222     def __saveStatus(self): 
    223         """ 
    224         Save relevant project information in a persistent way. 
    225         """ 
    226  
    227         if self.config: 
    228             self.config.saveProject(self) 
    229         else: 
    230             self.__saveOldStatus() 
    231  
    232     def __loadOldStatus(self): 
    233         from os.path import join, split 
    234  
    235         statusfilename = join(self.root, STATUS_FILENAME) 
    236         f = open(statusfilename) 
    237         self.source_kind = f.readline()[:-1] 
    238         self.target_kind = f.readline()[:-1] 
    239         self.upstream_module = f.readline()[:-1] 
    240         self.upstream_repos = f.readline()[:-1] 
    241         self.upstream_revision = f.readline()[:-1] 
    242         subdir = f.readline() 
    243         if subdir: 
    244             self.subdir = subdir[:-1] 
    245         else: 
    246             self.subdir = split(self.upstream_module)[1] 
    247         f.close() 
    248  
    249     def __loadStatus(self): 
    250         """ 
    251         Load relevant project information. 
    252         """ 
    253  
    254         if self.config: 
    255             self.config.loadProject(self) 
    256         else: 
    257             self.__loadOldStatus() 
    258  
    259         # Fix old configs 
    260  
    261         if self.source_kind == 'svn' and not '/' in self.upstream_module: 
    262             self.logger.warning('OLD config values for SVN') 
    263             print "The project at '%s' contains old values for" % self.root 
    264             print "the upstream repository (%s)" % self.upstream_repos 
    265             print "and module (%s)." % self.upstream_module 
    266             print "Please correct them, specifying the exact URL of the" 
    267             print "root of the SVN repository and then the prefix path up" 
    268             print "to the point you want, that must start with a slash." 
    269             print "This usually means splitting the repository URL above in" 
    270             print "two parts. For example, that could be" 
    271  
    272             crepo = self.upstream_repos 
    273             example_split = crepo.rfind('/', 6, crepo.rfind('/')) 
    274             if example_split > 0: 
    275                 example_repo = crepo[:example_split] 
    276                 example_module = crepo[example_split:] 
    277             else: 
    278                 example_repo = 'http://svn.plone.org/collective' 
    279                 example_module = '/ATContentTypes/trunk' 
    280  
    281             print "  Repository=%s" % example_repo 
    282             print "  Module=%s" % example_module 
    283             print "but your situation may vary, that's just an example!" 
    284             print 
    285             try: 
    286                 self.repository = raw_input('Repository: ') 
    287                 self.upstream_module = raw_input('Module/prefix: ') 
    288             except KeyboardInterrupt: 
    289                 self.logger.warning("Leaving old config values, stopped by user") 
    290                 raise 
    291  
    292     def bootstrap(self, source_kind, target_kind, 
    293                   source_repository, source_module, revision, 
    294                   target_repository, target_module, subdir): 
     32    def __init__(self, project): 
     33        self.project = project 
     34 
     35    def bootstrap(self): 
    29536        """ 
    29637        Bootstrap a new tailorized module. 
    29738 
    298         First of all prepare the target system working directory such that 
    299         it can host the upstream source tree. This is backend specific. 
    300  
    301         Extract a copy of the ``repository`` at given ``revision`` in the 
    302         ``root`` directory and initialize a target repository with its content. 
    303  
    304         The actual information on the project are stored in a text file. 
     39        First of all prepare the target system working directory such 
     40        that it can host the upstream source tree. This is backend 
     41        specific. 
     42 
     43        Then extract a copy of the upstream repository and import its 
     44        content into the target repository. 
    30545        """ 
    30646 
    307         from os.path import split 
    308  
    309         if source_kind == 'svn': 
    310             if not (source_module and source_module.startswith('/')): 
    311                 raise InvocationError('With SVN the module argument is ' 
    312                                       'mandatory and must start with a "/"') 
    313  
    314         if source_repository.endswith('/'): 
    315             source_repository = source_repository[:-1] 
    316  
    317         if source_module and source_module.endswith('/'): 
    318             source_module = source_module[:-1] 
    319  
    320         if not subdir: 
    321             subdir = split(source_module or source_repository)[1] or '' 
    322  
    323         self.logger.info("Bootstrapping '%s'" % (self.root,)) 
    324  
    325         dwd = DualWorkingDir(source_kind, target_kind) 
    326         self.logger.info("getting %s revision '%s' of '%s' from '%s'" % ( 
    327             source_kind, revision, source_module, source_repository)) 
     47        self.project.log_info("Bootstrapping '%s'" % self.project.root) 
    32848 
    32949        try: 
    330             dwd.prepareWorkingDirectory(self.root, 
    331                                         target_repository, target_module) 
     50            self.project.prepareWorkingDirectory() 
    33251        except: 
    333             self.logger.exception('Cannot prepare working directory!') 
     52            self.project.log_error('Cannot prepare working directory!', True) 
    33453            raise 
    33554 
    33655        try: 
    337             actual = dwd.checkoutUpstreamRevision(self.root, source_repository, 
    338                                                   source_module, revision, 
    339                                                   subdir=subdir, 
    340                                                   logger=self.logger) 
     56            self.project.checkoutUpstreamRevision() 
    34157        except: 
    342             self.logger.exception('Checkout failed!') 
     58            self.project.log_error("Checkout of '%s' failed!" % 
     59                                   self.project.name, True) 
    34360            raise 
    34461 
    345         # the above machinery checked out a copy under of the wc 
    346         # in the directory named as the last component of the module's name 
    347  
    348         if not source_module: 
    349             source_module = split(source_repository)[1] 
    350  
    351         try: 
    352             dwd.initializeNewWorkingDir(self.root, source_repository, 
    353                                         source_module, subdir, 
    354                                         actual, revision=='INITIAL') 
    355         except: 
    356             self.logger.exception('Working copy initialization failed!') 
    357             raise 
    358  
    359         self.source_kind = source_kind 
    360         self.target_kind = target_kind 
    361         self.upstream_repos = source_repository 
    362         self.upstream_module = source_module 
    363         self.subdir = subdir 
    364         self.upstream_revision = actual.revision 
    365  
    366         self.__saveStatus() 
    367  
    368         self.logger.info("Bootstrap completed") 
    369  
    370     def applyable(self, root, changeset): 
    371         """ 
    372         Print the changeset being applied. 
    373         """ 
    374  
    375         if self.verbose: 
    376             print "Changeset %s:" % changeset.revision 
    377             try: 
    378                 print changeset.log 
    379             except UnicodeEncodeError: 
    380                 print ">>> Non-printable changelog <<<" 
    381  
    382         return True 
    383  
    384     def applied(self, root, changeset): 
    385         """ 
    386         Save current status. 
    387         """ 
    388  
    389         self.upstream_revision = changeset.revision 
    390         self.__saveStatus() 
    391         if self.verbose: 
    392             print 
    393  
    394     def update(self, single_commit, concatenate_logs): 
     62        self.project.log_info("Bootstrap completed") 
     63 
     64    def update(self): 
    39565        """ 
    39666        Update an existing tailorized project. 
    397  
    398         Fetch the upstream changesets and apply them to the working copy. 
    399         Use the information stored in the ``tailor.info`` file to ask just 
    400         the new changeset since last bootstrap/synchronization. 
    40167        """ 
    40268 
    403         from os.path import join 
    404  
    405         self.__loadStatus() 
    406         proj = join(self.root, self.subdir) 
    407  
    408         self.logger.info("Updating '%s' from revision '%s'" % ( 
    409             self.upstream_module, self.upstream_revision)) 
    410  
    411         if self.verbose: 
    412             print "\nUpdating '%s' from revision '%s'" % ( 
    413                 self.upstream_module, self.upstream_revision) 
     69        self.project.log_info("Updating '%s'" % self.project.name) 
    41470 
    41571        try: 
    416             dwd = DualWorkingDir(self.source_kind, self.target_kind) 
    417             changesets = dwd.getPendingChangesets(proj, 
    418                                                   self.upstream_repos, 
    419                                                   self.upstream_module) 
    420         except KeyboardInterrupt: 
    421             print "Leaving '%s' unchanged" % proj 
    422             self.logger.info("Leaving '%s' unchanged, stopped by user" % proj) 
    423             return 
     72            self.project.applyPendingChangesets() 
    42473        except: 
    425             self.logger.exception("Unable to get changes for '%s'" % proj) 
     74            self.project.log_error("Cannot update '%s'!" % self.project.name, 
     75                                   True) 
    42676            raise 
    42777 
    428         nchanges = len(changesets) 
    429         if nchanges: 
    430             if self.verbose: 
    431                 print "Applying %d upstream changesets" % nchanges 
    432  
    433             try: 
    434                 last, conflicts = dwd.applyPendingChangesets( 
    435                     proj, self.upstream_module, 
    436                     applyable=self.applyable, 
    437                     applied=self.applied, logger=self.logger, 
    438                     delayed_commit=single_commit) 
    439             except: 
    440                 self.logger.exception('Upstream change application failed') 
    441                 raise 
    442  
    443             if last: 
    444                 if single_commit: 
    445                     dwd.commitDelayedChangesets(proj, concatenate_logs) 
    446  
    447                 self.logger.info("Update completed, now at revision '%s'" % ( 
    448                     self.upstream_revision,)) 
     78        self.project.log_info("Update completed") 
     79 
     80    def __call__(self, options): 
     81        if options.bootstrap: 
     82            self.bootstrap() 
    44983        else: 
    450             self.logger.info("Update completed with no upstream changes") 
    451  
     84            self.update() 
    45285 
    45386GENERAL_OPTIONS = [ 
     
    635268    Changeset.REFILL_MESSAGE = not options.dont_refill_changelogs 
    636269 
    637     SvnWorkingDir.USE_PROPSET = options.use_svn_propset 
    638  
    639270    if options.interactive: 
    640271        interactive(options, args) 
    641272    elif options.configfile: 
    642         config = TailorConfig(options) 
    643  
    644         config(map(abspath, args)) 
     273        defaults = {} 
     274        for k,v in options.__dict__.items(): 
     275            defaults[k.replace('_', '-')] = v 
     276 
     277        config = Config(open(options.configfile), defaults) 
     278 
     279        if not args: 
     280            args = config.projects() 
     281 
     282        for projname in args: 
     283            project = config[projname] 
     284            tailorizer = Tailorizer(project) 
     285            tailorizer(options) 
    645286    else: 
    646287        # Good (?) old way 
Note: See TracChangeset for help on using the changeset viewer.