source: tailor/vcpx/tailor.py @ 558

Revision 558, 14.2 KB checked in by lele@…, 8 years ago (diff)

Gives better meanings to --verbose and --debug when no config is given
When the config file is not given either thru --configfile or as the
argument specified on the command line, with no --verbose and no
--debug tailor prints a message and exits, with --verbose prints a
potential config file more or less equivalent to the given options,
while with --debug it goes on and execute the process, mainly for
testing purposes.

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