source: tailor/vcpx/tailor.py @ 1526

Revision 1526, 15.7 KB checked in by lele@…, 5 years ago (diff)

Bump up the version

RevLine 
[43]1# -*- mode: python; coding: utf-8 -*-
[45]2# :Progetto: vcpx -- Frontend capabilities
[43]3# :Creato:   dom 04 lug 2004 00:40:54 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
[305]5# :Licenza:  GNU General Public License
[442]6#
[43]7
8"""
[642]9Implement the frontend functionalities.
[43]10"""
11
12__docformat__ = 'reStructuredText'
13
[1526]14__version__ = '0.9.33'
[400]15
[1306]16from logging import getLogger
[677]17from optparse import OptionParser, OptionGroup, Option
[1178]18from vcpx import TailorException
[1179]19from vcpx.config import Config, ConfigurationError
20from vcpx.project import Project
21from vcpx.source import GetUpstreamChangesetsFailure
22
[43]23
[603]24class Tailorizer(Project):
[130]25    """
[485]26    A Tailorizer has two main capabilities: its able to bootstrap a
27    new Project, or brought it in sync with its current upstream
[45]28    revision.
29    """
[603]30
31    def _applyable(self, changeset):
32        """
33        Print the changeset being applied.
34        """
35
36        if self.verbose:
[986]37            self.log.info('Changeset "%s"', changeset.revision)
[940]38            if changeset.log:
[973]39                self.log.info("Log message: %s", changeset.log)
[940]40        self.log.debug("Going to apply changeset:\n%s", str(changeset))
[603]41        return True
42
43    def _applied(self, changeset):
44        """
[605]45        Separate changesets with an empty line.
[603]46        """
47
48        if self.verbose:
[940]49            self.log.info('-*'*30)
[603]50
[485]51    def bootstrap(self):
[124]52        """
[43]53        Bootstrap a new tailorized module.
54
[485]55        First of all prepare the target system working directory such
56        that it can host the upstream source tree. This is backend
57        specific.
[45]58
[485]59        Then extract a copy of the upstream repository and import its
60        content into the target repository.
[43]61        """
62
[973]63        self.log.info('Bootstrapping "%s" in "%s"', self.name, self.rootdir)
[120]64
[605]65        dwd = self.workingDir()
[452]66        try:
[605]67            dwd.prepareWorkingDirectory(self.source)
[452]68        except:
[1524]69            self.log.critical('Cannot prepare working directory!', exc_info=True)
[452]70            raise
71
[605]72        revision = self.config.get(self.name, 'start-revision', 'INITIAL')
73        try:
74            actual = dwd.checkoutUpstreamRevision(revision)
75        except:
[940]76            self.log.critical("Checkout of %s failed!", self.name)
[605]77            raise
78
[188]79        try:
[1188]80            dwd.importFirstRevision(self.source, actual, 'INITIAL'==revision)
[188]81        except:
[973]82            self.log.critical('Could not import checked out tree in "%s"!',
[1524]83                              self.rootdir, exc_info=True)
[188]84            raise
[442]85
[940]86        self.log.info("Bootstrap completed")
[43]87
[485]88    def update(self):
[45]89        """
90        Update an existing tailorized project.
91        """
[442]92
[973]93        self.log.info('Updating "%s" in "%s"', self.name, self.rootdir)
[221]94
[605]95        dwd = self.workingDir()
[232]96        try:
[605]97            pendings = dwd.getPendingChangesets()
98        except KeyboardInterrupt:
[982]99            self.log.warning('Leaving "%s" unchanged, stopped by user',
100                             self.name)
[835]101            raise
[222]102        except:
[1524]103            self.log.critical('Unable to get changes for "%s"',
104                              self.name, exc_info=True)
[222]105            raise
[442]106
[909]107        if pendings.pending():
[940]108            self.log.info("Applying pending upstream changesets")
[605]109
110            try:
111                last, conflicts = dwd.applyPendingChangesets(
112                    applyable=self._applyable, applied=self._applied)
113            except KeyboardInterrupt:
[982]114                self.log.warning('Leaving "%s" incomplete, stopped by user',
[940]115                                 self.name)
[835]116                raise
[605]117            except:
[1524]118                self.log.critical('Upstream change application failed',
119                                  exc_info=True)
[605]120                raise
121
122            if last:
[982]123                self.log.info('Update completed, now at revision "%s"',
[605]124                              last.revision)
125        else:
[940]126            self.log.info("Update completed with no upstream changes")
[188]127
[597]128    def __call__(self):
[493]129        from shwrap import ExternalCommand
[1113]130        from target import SynchronizableTargetWorkingDir
[493]131        from changes import Changeset
132
[665]133        def pconfig(option, raw=False):
134            return self.config.get(self.name, option, raw=raw)
[493]135
[601]136        ExternalCommand.DEBUG = pconfig('debug')
[493]137
[665]138        pname_format = pconfig('patch-name-format', raw=True)
[493]139        if pname_format is not None:
[1399]140            SynchronizableTargetWorkingDir.PATCH_NAME_FORMAT = pname_format.strip()
[1113]141        SynchronizableTargetWorkingDir.REMOVE_FIRST_LOG_LINE = pconfig('remove-first-log-line')
[722]142        Changeset.REFILL_MESSAGE = pconfig('refill-changelogs')
[493]143
[850]144        try:
145            if not self.exists():
146                self.bootstrap()
147                if pconfig('start-revision') == 'HEAD':
148                    return
149            self.update()
[1080]150        except (UnicodeDecodeError, UnicodeEncodeError), exc:
[1016]151            raise ConfigurationError('%s: it seems that the encoding '
152                                     'used by either the source ("%s") or the '
153                                     'target ("%s") repository '
[850]154                                     'cannot properly represent at least one '
155                                     'of the characters in the upstream '
156                                     'changelog. You need to use a wider '
[1016]157                                     'character set, using "encoding" option, '
158                                     'or even "encoding-errors-policy".'
159                                     % (exc, self.source.encoding,
160                                        self.target.encoding))
[605]161
[1180]162
[677]163class RecogOption(Option):
164    """
165    Make it possible to recognize an option explicitly given on the
166    command line from those simply coming out for their default value.
167    """
168
169    def process (self, opt, value, values, parser):
170        setattr(values, '__seen_' + self.dest, True)
171        return Option.process(self, opt, value, values, parser)
172
173
[116]174GENERAL_OPTIONS = [
[677]175    RecogOption("-D", "--debug", dest="debug",
[116]176                action="store_true", default=False,
[253]177                help="Print each executed command. This also keeps "
178                     "temporary files with the upstream logs, that are "
179                     "otherwise removed after use."),
[677]180    RecogOption("-v", "--verbose", dest="verbose",
[121]181                action="store_true", default=False,
182                help="Be verbose, echoing the changelog of each applied "
183                     "changeset to stdout."),
[1305]184    RecogOption("-c", "--configfile", metavar="CONFNAME",
[124]185                help="Centralized storage of projects info.  With this "
186                     "option and no other arguments tailor will update "
187                     "every project found in the config file."),
[677]188    RecogOption("--encoding", metavar="CHARSET", default=None,
[360]189                help="Force the output encoding to given CHARSET, rather "
[745]190                     "then using the user's default settings specified "
191                     "in the environment."),
[442]192]
[116]193
194UPDATE_OPTIONS = [
[677]195    RecogOption("-F", "--patch-name-format", metavar="FORMAT",
[230]196                help="Specify the prototype that will be used "
197                     "to compute the patch name.  The prototype may contain "
[512]198                     "%(keyword)s such as 'author', 'date', "
[511]199                     "'revision', 'firstlogline', 'remaininglog'. It "
[517]200                     "defaults to 'Tailorized \"%(revision)s\"'; "
[434]201                     "setting it to the empty string means that tailor will "
202                     "simply use the original changelog."),
[677]203    RecogOption("-1", "--remove-first-log-line", action="store_true",
[255]204                default=False,
205                help="Remove the first line of the upstream changelog. This "
[745]206                     "is intended to pair with --patch-name-format, "
207                     "when using its 'firstlogline' variable to build the "
[442]208                     "name of the patch."),
[722]209    RecogOption("-N", "--refill-changelogs", action="store_true",
[231]210                default=False,
[722]211                help="Refill every changelog, useful when upstream logs "
212                     "are not uniform."),
[116]213]
214
215BOOTSTRAP_OPTIONS = [
[677]216    RecogOption("-s", "--source-kind", dest="source_kind", metavar="VC-KIND",
[43]217                help="Select the backend for the upstream source "
218                     "version control VC-KIND. Default is 'cvs'.",
219                default="cvs"),
[677]220    RecogOption("-t", "--target-kind", dest="target_kind", metavar="VC-KIND",
[43]221                help="Select VC-KIND as backend for the shadow repository, "
222                     "with 'darcs' as default.",
[106]223                default="darcs"),
[677]224    RecogOption("-R", "--repository", "--source-repository",
[446]225                dest="source_repository", metavar="REPOS",
[106]226                help="Specify the upstream repository, from where bootstrap "
227                     "will checkout the module.  REPOS syntax depends on "
228                     "the source version control kind."),
[677]229    RecogOption("-m", "--module", "--source-module", dest="source_module",
[446]230                metavar="MODULE",
[206]231                help="Specify the module to checkout at bootstrap time. "
[247]232                     "This has different meanings under the various upstream "
233                     "systems: with CVS it indicates the module, while under "
234                     "SVN it's the prefix of the tree you want and must begin "
235                     "with a slash. Since it's used in the description of the "
236                     "target repository, you may want to give it a value with "
[745]237                     "darcs too, even though it is otherwise ignored."),
[677]238    RecogOption("-r", "--revision", "--start-revision", dest="start_revision",
[492]239                metavar="REV",
[106]240                help="Specify the revision bootstrap should checkout.  REV "
241                     "must be a valid 'name' for a revision in the upstream "
[274]242                     "version control kind. For CVS it may be either a branch "
[408]243                     "name, a timestamp or both separated by a space, and "
244                     "timestamp may be 'INITIAL' to denote the beginning of "
[431]245                     "time for the given branch. Under Darcs, INITIAL is a "
246                     "shortcut for the name of the first patch in the upstream "
247                     "repository, otherwise it is interpreted as the name of "
[432]248                     "a tag. Under Subversion, 'INITIAL' is the first patch "
249                     "that touches given repos/module, otherwise it must be "
250                     "an integer revision number. "
[668]251                     "'HEAD' means the latest version in all backends.",
252                default="INITIAL"),
[677]253    RecogOption("-T", "--target-repository",
[452]254                dest="target_repository", metavar="REPOS", default=None,
255                help="Specify the target repository, the one that will "
256                     "receive the patches coming from the source one."),
[677]257    RecogOption("-M", "--target-module", dest="target_module",
[452]258                metavar="MODULE",
259                help="Specify the module on the target repository that will "
260                     "actually contain the upstream source tree."),
[677]261    RecogOption("--subdir", metavar="DIR",
[145]262                help="Force the subdirectory where the checkout will happen, "
263                     "by default it's the tail part of the module name."),
[43]264]
265
[429]266VC_SPECIFIC_OPTIONS = [
[1272]267    RecogOption("--use-propset", action="store_true", default=False,
[628]268                dest="use_propset",
[429]269                help="Use 'svn propset' to set the real date and author of "
270                     "each commit, instead of appending these information to "
271                     "the changelog. This requires some tweaks on the SVN "
272                     "repository to enable revision propchanges."),
[695]273    RecogOption("--ignore-arch-ids", action="store_true", default=False,
274                dest="ignore_ids",
275                help="Ignore .arch-ids directories when using a tla source."),
[429]276]
[442]277
[1178]278
279class ExistingProjectError(TailorException):
[194]280    "Project seems already tailored"
[43]281
[1178]282
283class ProjectNotTailored(TailorException):
[194]284    "Not a tailored project"
[442]285
[1180]286
[43]287def main():
288    """
289    Script entry point.
290
291    Parse the command line options and arguments, and for each
292    specified working copy directory (the current working directory by
293    default) execute the tailorization steps.
294    """
[442]295
[502]296    import sys
[493]297    from os import getcwd
[442]298
[1477]299    usage = "usage: \n\
300       1. %prog [options] [project ...]\n\
301       2. %prog test [--help] [...]"
302    parser = OptionParser(usage=usage,
[400]303                          version=__version__,
[116]304                          option_list=GENERAL_OPTIONS)
[442]305
[116]306    bsoptions = OptionGroup(parser, "Bootstrap options")
307    bsoptions.add_options(BOOTSTRAP_OPTIONS)
308
309    upoptions = OptionGroup(parser, "Update options")
310    upoptions.add_options(UPDATE_OPTIONS)
[429]311
312    vcoptions = OptionGroup(parser, "VC specific options")
313    vcoptions.add_options(VC_SPECIFIC_OPTIONS)
[442]314
[116]315    parser.add_option_group(bsoptions)
316    parser.add_option_group(upoptions)
[429]317    parser.add_option_group(vcoptions)
[442]318
[43]319    options, args = parser.parse_args()
[442]320
[643]321    defaults = {}
322    for k,v in options.__dict__.items():
[677]323        if k.startswith('__'):
324            continue
325        if k <> 'configfile' and hasattr(options, '__seen_' + k):
[643]326            defaults[k.replace('_', '-')] = str(v)
[485]327
[643]328    if options.configfile or (len(sys.argv)==2 and len(args)==1):
329        # Either we have a --configfile, or there are no options
330        # and a single argument (to support shebang style scripts)
[502]331
[643]332        if not options.configfile:
333            options.configfile = sys.argv[1]
334            args = None
[502]335
[643]336        config = Config(open(options.configfile), defaults)
[485]337
[643]338        if not args:
339            args = config.projects()
[48]340
[643]341        for projname in args:
342            tailorizer = Tailorizer(projname, config)
[1054]343            try:
344                tailorizer()
345            except GetUpstreamChangesetsFailure:
346                # Do not stop on this kind of error, but keep going
347                pass
[643]348    else:
349        for omit in ['source-kind', 'target-kind',
350                     'source-module', 'target-module',
351                     'source-repository', 'target-repository',
352                     'start-revision', 'subdir']:
353            if omit in defaults:
354                del defaults[omit]
[557]355
[643]356        config = Config(None, defaults)
[442]357
[643]358        config.add_section('project')
359        source = options.source_kind + ':source'
360        config.set('project', 'source', source)
361        target = options.target_kind + ':target'
362        config.set('project', 'target', target)
363        config.set('project', 'root-directory', getcwd())
364        config.set('project', 'subdir', options.subdir or '.')
365        config.set('project', 'state-file', 'tailor.state')
366        config.set('project', 'start-revision', options.start_revision)
[442]367
[643]368        config.add_section(source)
[1306]369        if options.source_repository:
370            config.set(source, 'repository', options.source_repository)
371        else:
372            logger = getLogger('tailor')
373            logger.warning("By any chance you forgot either the --source-repository or the --configfile option...")
374
[643]375        if options.source_module:
376            config.set(source, 'module', options.source_module)
[116]377
[643]378        config.add_section(target)
379        if options.target_repository:
380            config.set(target, 'repository', options.target_repository)
381        if options.target_module:
382            config.set(target, 'module', options.target_module)
383
384        if options.verbose:
385            sys.stderr.write("You should put the following configuration "
386                             "in some file, adjust it as needed\n"
387                             "and use --configfile option with that "
388                             "file as argument:\n")
[124]389            config.write(sys.stdout)
[643]390
391        if options.debug:
392            tailorizer = Tailorizer('project', config)
393            tailorizer()
394        elif not options.verbose:
[124]395            sys.stderr.write("Operation not performed, try --verbose\n")
Note: See TracBrowser for help on using the repository browser.