source: tailor/vcpx/tailor.py @ 604

Revision 604, 15.4 KB checked in by lele@…, 8 years ago (diff)

Drop the --migrate-config option

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