source: tailor/vcpx/tailor.py @ 668

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

Now start-revision defaults to INITIAL, not HEAD

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.8'
15
16from optparse import OptionParser, OptionGroup, make_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 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 = not pconfig('dont-refill-changelogs')
152
153        if not self.exists():
154            self.bootstrap()
155            if pconfig('start-revision') == 'HEAD':
156                return
157        self.update()
158
159
160GENERAL_OPTIONS = [
161    make_option("-D", "--debug", dest="debug",
162                action="store_true", default=False,
163                help="Print each executed command. This also keeps "
164                     "temporary files with the upstream logs, that are "
165                     "otherwise removed after use."),
166    make_option("-v", "--verbose", dest="verbose",
167                action="store_true", default=False,
168                help="Be verbose, echoing the changelog of each applied "
169                     "changeset to stdout."),
170    make_option("--configfile", metavar="CONFNAME",
171                help="Centralized storage of projects info.  With this "
172                     "option and no other arguments tailor will update "
173                     "every project found in the config file."),
174    make_option("--encoding", metavar="CHARSET", default=None,
175                help="Force the output encoding to given CHARSET, rather "
176                     "then using the user default settings specified in the "
177                     "environment."),
178]
179
180UPDATE_OPTIONS = [
181    make_option("-F", "--patch-name-format", metavar="FORMAT",
182                help="Specify the prototype that will be used "
183                     "to compute the patch name.  The prototype may contain "
184                     "%(keyword)s such as 'author', 'date', "
185                     "'revision', 'firstlogline', 'remaininglog'. It "
186                     "defaults to 'Tailorized \"%(revision)s\"'; "
187                     "setting it to the empty string means that tailor will "
188                     "simply use the original changelog."),
189    make_option("-1", "--remove-first-log-line", action="store_true",
190                default=False,
191                help="Remove the first line of the upstream changelog. This "
192                     "is intended to go in pair with --patch-name-format, "
193                     "when using it's 'firstlogline' variable to build the "
194                     "name of the patch."),
195    make_option("-N", "--dont-refill-changelogs", action="store_true",
196                default=False,
197                help="Do not refill every changelog, but keep them as is. "
198                     "This is usefull when using --patch-name-format, or "
199                     "when upstream developers are already formatting their "
200                     "notes with a consistent layout."),
201]
202
203BOOTSTRAP_OPTIONS = [
204    make_option("-s", "--source-kind", dest="source_kind", metavar="VC-KIND",
205                help="Select the backend for the upstream source "
206                     "version control VC-KIND. Default is 'cvs'.",
207                default="cvs"),
208    make_option("-t", "--target-kind", dest="target_kind", metavar="VC-KIND",
209                help="Select VC-KIND as backend for the shadow repository, "
210                     "with 'darcs' as default.",
211                default="darcs"),
212    make_option("-R", "--repository", "--source-repository",
213                dest="source_repository", metavar="REPOS",
214                help="Specify the upstream repository, from where bootstrap "
215                     "will checkout the module.  REPOS syntax depends on "
216                     "the source version control kind."),
217    make_option("-m", "--module", "--source-module", dest="source_module",
218                metavar="MODULE",
219                help="Specify the module to checkout at bootstrap time. "
220                     "This has different meanings under the various upstream "
221                     "systems: with CVS it indicates the module, while under "
222                     "SVN it's the prefix of the tree you want and must begin "
223                     "with a slash. Since it's used in the description of the "
224                     "target repository, you may want to give it a value with "
225                     "darcs too even if it is otherwise ignored."),
226    make_option("-r", "--revision", "--start-revision", dest="start_revision",
227                metavar="REV",
228                help="Specify the revision bootstrap should checkout.  REV "
229                     "must be a valid 'name' for a revision in the upstream "
230                     "version control kind. For CVS it may be either a branch "
231                     "name, a timestamp or both separated by a space, and "
232                     "timestamp may be 'INITIAL' to denote the beginning of "
233                     "time for the given branch. Under Darcs, INITIAL is a "
234                     "shortcut for the name of the first patch in the upstream "
235                     "repository, otherwise it is interpreted as the name of "
236                     "a tag. Under Subversion, 'INITIAL' is the first patch "
237                     "that touches given repos/module, otherwise it must be "
238                     "an integer revision number. "
239                     "'HEAD' means the latest version in all backends.",
240                default="INITIAL"),
241    make_option("-T", "--target-repository",
242                dest="target_repository", metavar="REPOS", default=None,
243                help="Specify the target repository, the one that will "
244                     "receive the patches coming from the source one."),
245    make_option("-M", "--target-module", dest="target_module",
246                metavar="MODULE",
247                help="Specify the module on the target repository that will "
248                     "actually contain the upstream source tree."),
249    make_option("--subdir", metavar="DIR",
250                help="Force the subdirectory where the checkout will happen, "
251                     "by default it's the tail part of the module name."),
252]
253
254VC_SPECIFIC_OPTIONS = [
255    make_option("--use-svn-propset", action="store_true", default=False,
256                dest="use_propset",
257                help="Use 'svn propset' to set the real date and author of "
258                     "each commit, instead of appending these information to "
259                     "the changelog. This requires some tweaks on the SVN "
260                     "repository to enable revision propchanges."),
261]
262
263class ExistingProjectError(Exception):
264    "Project seems already tailored"
265
266class ProjectNotTailored(Exception):
267    "Not a tailored project"
268
269def main():
270    """
271    Script entry point.
272
273    Parse the command line options and arguments, and for each
274    specified working copy directory (the current working directory by
275    default) execute the tailorization steps.
276    """
277
278    import sys
279    from os import getcwd
280
281    parser = OptionParser(usage='%prog [options] [project ...]',
282                          version=__version__,
283                          option_list=GENERAL_OPTIONS)
284
285    bsoptions = OptionGroup(parser, "Bootstrap options")
286    bsoptions.add_options(BOOTSTRAP_OPTIONS)
287
288    upoptions = OptionGroup(parser, "Update options")
289    upoptions.add_options(UPDATE_OPTIONS)
290
291    vcoptions = OptionGroup(parser, "VC specific options")
292    vcoptions.add_options(VC_SPECIFIC_OPTIONS)
293
294    parser.add_option_group(bsoptions)
295    parser.add_option_group(upoptions)
296    parser.add_option_group(vcoptions)
297
298    options, args = parser.parse_args()
299
300    defaults = {}
301    for k,v in options.__dict__.items():
302        if k <> 'configfile':
303            defaults[k.replace('_', '-')] = str(v)
304
305    if options.configfile or (len(sys.argv)==2 and len(args)==1):
306        # Either we have a --configfile, or there are no options
307        # and a single argument (to support shebang style scripts)
308
309        if not options.configfile:
310            options.configfile = sys.argv[1]
311            args = None
312
313        config = Config(open(options.configfile), defaults)
314
315        if not args:
316            args = config.projects()
317
318        for projname in args:
319            tailorizer = Tailorizer(projname, config)
320            tailorizer()
321    else:
322        for omit in ['source-kind', 'target-kind',
323                     'source-module', 'target-module',
324                     'source-repository', 'target-repository',
325                     'start-revision', 'subdir']:
326            if omit in defaults:
327                del defaults[omit]
328
329        config = Config(None, defaults)
330
331        config.add_section('project')
332        source = options.source_kind + ':source'
333        config.set('project', 'source', source)
334        target = options.target_kind + ':target'
335        config.set('project', 'target', target)
336        config.set('project', 'root-directory', getcwd())
337        config.set('project', 'subdir', options.subdir or '.')
338        config.set('project', 'state-file', 'tailor.state')
339        config.set('project', 'start-revision', options.start_revision)
340
341        config.add_section(source)
342        config.set(source, 'repository', options.source_repository)
343        if options.source_module:
344            config.set(source, 'module', options.source_module)
345
346        config.add_section(target)
347        if options.target_repository:
348            config.set(target, 'repository', options.target_repository)
349        if options.target_module:
350            config.set(target, 'module', options.target_module)
351
352        if options.verbose:
353            import sys
354
355            sys.stderr.write("You should put the following configuration "
356                             "in some file, adjust it as needed\n"
357                             "and use --configfile option with that "
358                             "file as argument:\n")
359            config.write(sys.stdout)
360
361        if options.debug:
362            tailorizer = Tailorizer('project', config)
363            tailorizer()
364        elif not options.verbose:
365            sys.stderr.write("Operation not performed, try --verbose\n")
Note: See TracBrowser for help on using the repository browser.