source: pyutilib.svn/trunk/pyutilib/svn/svnpm.py @ 2832

Revision 2832, 21.0 KB checked in by wehart, 2 years ago (diff)

Portability fixes for Python 3.x

  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2#  _________________________________________________________________________
3#
4#  PyUtilib: A Python utility library.
5#  Copyright (c) 2008 Sandia Corporation.
6#  This software is distributed under the BSD License.
7#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
8#  the U.S. Government retains certain rights in this software.
9#  _________________________________________________________________________
10#
11
12import logging
13import os
14import re
15import sys
16import yaml
17from optparse import OptionParser, IndentedHelpFormatter
18from pyutilib.svn.core import log
19from pyutilib.svn.database import RepositoryDatabase, DatabaseError
20from pyutilib.svn.external_manager import ExternalManager
21
22class CommandParserCommand(Exception):
23    def __init__(self, msg):
24        self.msg = msg
25    def __str__(self):
26        return self.msg
27
28class CommandParser(OptionParser):
29    def error(self, what):
30        g = re.match('no such option: (.+)', what)
31        if g:
32            raise CommandParserCommand(g.group(1))
33        OptionParser.error(self, what)
34
35class DescriptionFormatter(IndentedHelpFormatter):
36    def format_description(self, description):
37        parts = description.split("\n\n")
38        ans = ""
39        for part in parts:
40            tmp = IndentedHelpFormatter.format_description(self, part)
41            if tmp and tmp[0] != " " and len(ans):
42                ans += "\n" + tmp
43            else:
44                ans += tmp
45        return ans
46
47
48class ScriptCommand(object):
49    def help(self, pm):
50        pass
51    def register_options(self, parser):
52        pass
53    def process(self, pm, options, args):
54        pass
55
56
57class help_cmd(ScriptCommand):
58    def help(self, pm):
59        return """ \
60%prog: a utility for managing externals-based projects within
61Subversion.  Specific help for individual subcommands is available by
62running '%prog help <subcommand>'.
63
64Available subcommands:\n\n   """ + "\n\n   ".join(sorted(pm.commands.keys()))
65
66    def process(self, pm, options, args):
67        if len(args) > 1:
68            cmd = args[1]
69            if cmd not in pm.commands:
70                print("Unknown command: " + cmd)
71                return 2
72            info = cmd.upper() + ": " + ( pm.commands[cmd].help(pm) or
73                                          "(no description provided)")
74            parser = OptionParser(usage=pm.usage,
75                                  description=info,
76                                  formatter=DescriptionFormatter())
77            pm.common_args(parser)
78            pm.commands[cmd].register_options(parser)
79            parser.print_help()
80        else:
81            self.process(pm, options, ['help', 'help'])
82
83
84class CommandManager(object):
85    def __init__(self):
86        self.commands = {}
87        self.register_command("help", help_cmd())
88        self.usage = """usage: %prog subcommand [options] [args]
89Type '%prog help <subcommand>' for help on a specific subcommand."""
90
91    def register_command(self, name, cmd):
92        if name in self.commands:
93            raise ProjectManager_Error("duplicate command name: %s" % name)
94        self.commands[name] = cmd
95
96    def common_args(self, parser):
97        parser.add_option('--verbose', '-v', action="count", dest='verbose',
98                          default=CommandManager.default_verbosity(),
99                          help="increase verbosity; "
100                          "can be specified multiple times")
101        parser.add_option('--quiet', '-q', action="store_true", dest='quiet',
102                          help="silence all messages except for errors")
103
104    @staticmethod
105    def default_verbosity():
106        return 1
107
108    def process(self, args=None):
109        if args is None:
110            args = sys.argv[1:]
111        else:
112            args = args[:]
113
114        # We need to preemptively parse the command line to try and
115        # determine the command name
116        parser = CommandParser(usage=self.usage)
117        self.common_args(parser)
118        try:
119            options, largs = parser.parse_args(args[:])
120        except CommandParserCommand:
121            e = sys.exc_info()[0]
122            if len(parser.largs):
123                largs = parser.largs
124            else:
125                largs = [e.msg]
126
127        if len(largs) == 0:
128            log.error("No subcommand specified")
129            parser.print_usage()
130            return 2
131
132        command = largs[0]
133        if command not in self.commands:
134            log.error("Unknown subcommand: " + command)
135            parser.print_usage()
136            return 2
137
138        # now that we know the command, pars the args for real
139        parser = OptionParser(usage=self.usage)
140        self.common_args(parser)
141        self.commands[command].register_options(parser)
142        options, largs = parser.parse_args(args[:])
143
144        if not len(largs) or largs[0] != command:
145            log.error( "My understanding of the subcommand changed between "
146                       "preprocessing (%s) and runtime (%s)" %
147                       ( command, len(largs) and "None" or largs[0] ) )
148            return 1
149
150        # process common options
151        if options.quiet or options.verbose == 0:
152            log.setLevel(logging.ERROR)
153        elif options.verbose == 1:
154            log.setLevel(logging.WARNING)
155        elif options.verbose == 2:
156            log.setLevel(logging.INFO)
157        elif options.verbose >= 3:
158            log.setLevel(logging.DEBUG)
159
160        # run the command
161        return self.commands[command].process(self, options, largs)
162
163
164
165class db_cmd(ScriptCommand):
166    def help(self, pm):
167        return """
168Perform administration on the cached database of Subversion
169repository information."""
170
171    def register_options(self, parser):
172        parser.add_option('--add', '-a', action="append", dest='add',
173                          default=[],
174                          help="add a new repository to the database")
175        parser.add_option('--remove', action="append", dest='remove',
176                          default=[],
177                          help="remove a repository from the database")
178        parser.add_option('--update', '-u', action="store_true", dest="update",
179                          help="update the repository database cache")
180        parser.add_option('--rebuild', action="store_true", dest="rebuild",
181                          help="rebuild the repository database cache")
182        parser.add_option('--print', '-p', action="store_true", dest="printdb",
183                          help="print the repository database")
184        parser.add_option('--list', '-l', action="append", dest="list",
185                          default=[],
186                          help="list the specified database components "\
187                              "[repos, projects, targets]")
188
189    def process(self, pm, options, args):
190        db = RepositoryDatabase(options.db)
191        try:
192            db.load()
193        except DatabaseError:
194            log.info("cache file DNE; initializing empty database")
195
196        for url in options.add:
197            db.add(url)
198            db.save()
199        for url in options.remove:
200            db.remove(url)
201            db.save()
202        if options.rebuild:
203            db.rescan()
204            db.save()
205        if options.update:
206            if db.update():
207                db.save()
208        if options.printdb:
209            print(db)
210        for component in options.list:
211            if component == 'repos':
212                print("\n".join(sorted(db.repos.keys())))
213            elif component == 'projects':
214                for r in sorted(db.repos.keys()):
215                    print("\n".join(sorted([ x.url for x in
216                                             db.repos[r].projects.values() ])))
217            elif component == 'targets':
218                for r in sorted(db.repos.keys()):
219                    for p in sorted(db.repos[r].projects.keys()):
220                        print("\n".join(sorted([x.url for x in
221                                   db.repos[r].projects[p].targets.values()])))
222            else:
223                log.error("Unknown component " + component)
224
225
226class update_cmd(ScriptCommand):
227    def help(self, pm):
228        return """
229Recursively update all repositories in the repository database cache.
230This subcommand will update all repositories in the database and then
231recursively parse all externals in each repository and add the
232repositories that those externals point to to the repository database.
233Pay special attention to the 'aliases; and 'exclude' sections of the
234configuration file to prevent repositories from being included multiple
235times through different URLs, or to exclude repositories completely."""
236
237    def register_options(self, parser):
238        parser.add_option('--config', '-c', action="store", dest='config',
239                          help="the location of the configuration file "
240                          "(default=%s)" % ExternalManager.default_config())
241
242    def process(self, pm, options, args):
243        em = ExternalManager(options.config)
244        em.update()
245
246
247class check_cmd(ScriptCommand):
248    def help(self, pm):
249        return """
250Check the integrity of all externals in the repository database.  This
251will identify issues with externals, including:
252
253  - externals that point through an aliased URL.
254
255  - externals that point to an excluded URL.
256
257  - externals that point to a location other than a known project target.
258
259  - pegged externals that work but point to locations that no longer
260exist at the HEAD.
261
262  - externals that are pegged to revisions older than HEAD for that location.
263
264  - externals that should be pegged, but are not.
265"""
266
267    def register_options(self, parser):
268        parser.add_option('--config', '-c', action="store", dest='config',
269                          help="the location of the configuration file "
270                          "(default=%s)" % ExternalManager.default_config())
271        parser.add_option('--peg', action="store_true", dest='peg',
272                          help="validate that all externals referenced "
273                          "through pegged targets are also pegged")
274        parser.add_option('--key', '-k', action="store_true", dest='key',
275                          help="print a key to all the symbols used")
276        parser.add_option('--project', '-p', action="store", dest='project',
277                          help="only check specified project")
278        parser.add_option('--repo', '-r', action="store", dest='repo',
279                          help="only check specified repository")
280
281    def process(self, pm, options, args):
282        em = ExternalManager(options.config)
283        em.resolve_targets()
284        proj = None
285        repo = None
286        if options.project:
287            proj = em.get_project(options.project)
288            if proj is None:
289                log.error( "Specified project (%s) does not exist" %
290                           options.project )
291                return 1
292        if options.repo:
293            repo = em.get_repository(options.repo)
294            if repo is None:
295                log.error( "Specified repository (%s) does not exist" %
296                           options.repo )
297                return 1
298        if options.peg:
299            em.check_pegged(project=proj, repo=repo, key=options.key)
300        else:
301            em.check_integrity(project=proj, repo=repo, key=options.key)
302
303
304class status_cmd(ScriptCommand):
305    def help(self, pm):
306        return """
307Print information on the status for projects in the repository database.
308"""
309
310    def register_options(self, parser):
311        parser.add_option('--config', '-c', action="store", dest='config',
312                          help="the location of the configuration file "
313                          "(default=%s)" % ExternalManager.default_config())
314        parser.add_option('--project', '-p', action="store", dest='project',
315                          help="only check specified project")
316        parser.add_option('--repo', '-r', action="store", dest='repo',
317                          help="only check specified repository")
318
319    def process(self, pm, options, args):
320        em = ExternalManager(options.config)
321        em.resolve_targets()
322        proj = None
323        repo = None
324        if options.project:
325            proj = em.get_project(options.project)
326            if proj is None:
327                log.error( "Specified project (%s) does not exist" %
328                           options.project )
329                return 1
330        if options.repo:
331            repo = em.get_repository(options.repo)
332            if repo is None:
333                log.error( "Specified repository (%s) does not exist" %
334                           options.repo )
335                return 1
336        em.print_status(repo=repo, project=proj)
337
338
339class diff_cmd(ScriptCommand):
340    def help(self, pm):
341        return """
342Print a difference between the specified repository definition and the
343current state of the repository (as stored in the repository database).
344
345usage: %prog diff repo_defn [options] [args]
346
347   repo_defn : the YAML repository definition file to compare against
348"""
349
350    def register_options(self, parser):
351        parser.add_option('--config', '-c', action="store", dest='config',
352                          help="the location of the configuration file "
353                          "(default=%s)" % ExternalManager.default_config())
354
355    def process(self, pm, options, args):
356        fname = len(args) > 1 and args[1] or None
357        if not fname or not os.path.exists( fname ):
358            log.error("Definition file (%s) does not exist" % str(fname))
359            return 1
360
361        em = ExternalManager(options.config)
362        em.resolve_targets()
363        user_description = em.load_repo_definition(fname)
364        description = em.finalize_repo_definition(user_description)
365        changes = em.generate_changeset_from_definition(description)
366        em.print_changeset(changes)
367
368
369class inspect_cmd(ScriptCommand):
370    def help(self, pm):
371        return """
372Generate the YAML repository definition for the specified Subversion
373repository.  It is HIGHLY recommended that you
374update your repository database cache BEFORE running this command.
375
376usage: %prog inspect repo_defn [options]
377
378   repo_defn : the YAML repository definition file to generate
379"""
380
381    def register_options(self, parser):
382        parser.add_option('--config', '-c', action="store", dest='config',
383                          help="the location of the configuration file "
384                          "(default=%s)" % ExternalManager.default_config())
385        parser.add_option('--project', '-p', action="store", dest='project',
386                          help="generate description for the specified project")
387        parser.add_option('--repo', '-r', action="store", dest='repo',
388                          help="generate description for the specified repository")
389
390    def process(self, pm, options, args):
391        fname = len(args) > 1 and args[1] or None
392        if not fname:
393            log.error("Definition file was not specified.")
394            return 1
395        if os.path.exists( fname ):
396            log.error("Definition file (%s) exists" % str(fname))
397            return 1
398
399        em = ExternalManager(options.config)
400        em.resolve_targets()
401
402        proj = None
403        repo = None
404        if options.project:
405            proj = em.get_project(options.project)
406            if proj is None:
407                log.error( "Specified project (%s) does not exist" %
408                           options.project )
409                return 1
410        if options.repo:
411            repo = em.get_repository(options.repo)
412            if repo is None:
413                log.error( "Specified repository (%s) does not exist" %
414                           options.repo )
415                return 1
416
417        descrip = em.generate_repo_definition(proj=proj, repo=repo)
418        final = em.finalize_repo_definition(descrip)
419        ans = em.collapse_repo_definition(final)
420        log.info("Writing description to " + fname)
421        FILE = open(fname, 'w')
422        FILE.write( yaml.dump( ans, default_flow_style=False,
423                               explicit_start=True, explicit_end=True,
424                               width=160 ).replace('{}','') )
425        FILE.close()
426
427
428class apply_cmd(ScriptCommand):
429    def help(self, pm):
430        return """
431Create a working copy and apply any changes necessary to match the
432specified repository definition.  It is HIGHLY recommended that you
433update your repository database cache BEFORE running this command.
434
435usage: %prog apply repo_defn [options] [args]
436
437   repo_defn : the YAML repository definition file to compare against
438"""
439
440    def register_options(self, parser):
441        parser.add_option('--config', '-c', action="store", dest='config',
442                          help="the location of the configuration file "
443                          "(default=%s)" % ExternalManager.default_config())
444
445    def process(self, pm, options, args):
446        fname = len(args) > 1 and args[1] or None
447        if not fname or not os.path.exists( fname ):
448            log.error("Definition file (%s) does not exist" % str(fname))
449            return 1
450
451        em = ExternalManager(options.config)
452        em.resolve_targets()
453        user_description = em.load_repo_definition(fname)
454        description = em.finalize_repo_definition(user_description)
455        changes = em.generate_changeset_from_definition(description)
456        em.implement_changeset(changes, description)
457
458
459
460class branch_cmd(ScriptCommand):
461    def help(self, pm):
462        return """
463Prepare a branch of an existing project.
464
465usage: %prog branch repo_defn [options] [args]
466
467   repo_defn : the YAML repository definition file to update with the
468               branch configuration
469"""
470
471    def register_options(self, parser):
472        parser.add_option('--config', '-c', action="store", dest='config',
473                          help="the location of the configuration file "
474                          "(default=%s)" % ExternalManager.default_config())
475        parser.add_option('--project', '-p', action="store", dest='project',
476                          help="project to branch")
477        parser.add_option('--source', action="store", dest='source',
478                          help="source target (target to be copied)")
479        parser.add_option('--dest', action="store", dest='dest',
480                          help="destination target (target to be created)")
481        parser.add_option('--peg', action="store_true", dest='peg',
482                          help="peg all externals to fixed revision numbers")
483        parser.add_option('--dry-run', action="store_true", dest='dryrun',
484                          help="configure branch, but do not add it to "
485                          "the definition file")
486
487    def process(self, pm, options, args):
488        fname = len(args) > 1 and args[1] or None
489        if not fname or not os.path.exists( fname ):
490            log.error("Definition file (%s) does not exist" % str(fname))
491            return 1
492
493        em = ExternalManager(options.config)
494        em.resolve_targets()
495
496        branch_cfg = {'peg' : options.peg}
497        if options.project:
498            branch_cfg['project'] = options.project
499        if options.source:
500            branch_cfg['src'] = options.source
501        if options.dest:
502            branch_cfg['target'] = options.dest
503        defn, proj_name = em.branch_config(branch_cfg)
504
505        tmp = em.collapse_repo_definition(defn)
506        log.info("Configured branch: \n" + yaml.dump(
507            tmp, default_flow_style=False,
508            explicit_start=True, explicit_end=True,
509            width=160 ).replace('{}',''))
510
511        if options.dryrun:
512            return 0
513
514        user_description = em.load_repo_definition(fname)
515        if defn['__repo__'] != user_description['__repo__']:
516            log.error("Definition file does not match branch repository")
517            return 1
518        user_description[proj_name].update(defn[proj_name])
519        ans = em.collapse_repo_definition(user_description)
520        log.info("Writing description to " + fname)
521        FILE = open(fname, 'w')
522        FILE.write( yaml.dump( ans, default_flow_style=False,
523                               explicit_start=True, explicit_end=True,
524                               width=160 ).replace('{}','') )
525        FILE.close()
526
527        #description = em.finalize_repo_definition(user_description)
528        #changes = em.generate_changeset_from_definition(description)
529        #em.print_changeset(changes)
530
531
532class SVNPM_CommandManager(CommandManager):
533    def __init__(self):
534        CommandManager.__init__(self)
535        self.register_command("db",      db_cmd())
536        self.register_command("update",  update_cmd())
537        self.register_command("check",   check_cmd())
538        self.register_command("diff",    diff_cmd())
539        self.register_command("inspect", inspect_cmd())
540        self.register_command("apply",   apply_cmd())
541        self.register_command("status",  status_cmd())
542        self.register_command("branch",  branch_cmd())
543
544    def common_args(self, parser):
545        CommandManager.common_args(self, parser)
546        parser.add_option('--database','-d', action="store", dest='db',
547                          default=RepositoryDatabase.default_db(),
548                          help="the repository database file "
549                          "(default=%default)")
550
551
552def main():
553    pm = SVNPM_CommandManager()
554    sys.exit(pm.process())
555
556if __name__ == '__main__':
557    main()
Note: See TracBrowser for help on using the repository browser.