source: tailor/vcpx/tailor.py @ 745

Revision 745, 14.7 KB checked in by lele@…, 8 years ago (diff)

Forward port of documentation fixes by D.Love

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