source: tailor/vcpx/tailor.py @ 1399

Revision 1399, 15.5 KB checked in by henry@…, 6 years ago (diff)

[Tailor] patch-name-format empty was not working

Changeset [1359] has not fixed it.
Please don't add the revision number, if the user wand to leave empty it.

Examples for no patchname:

patch-name-format =
patch-name-format = ""

The empty string "" is more understandable as a blank space after the '='.

This example is more for internal interestings, it set default patchname:

patch-name-format = None

All other values, set user specifics.

Tested for Monotone and Subversion. All others have similar code style,
so that should work in all repositories.

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