source: tailor/vcpx/tailor.py @ 605

Revision 605, 14.8 KB checked in by lele@…, 8 years ago (diff)

Inlined simple proxy methods

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