source: tailor/vcpx/tailor.py @ 121

Revision 121, 11.3 KB checked in by lele@…, 9 years ago (diff)

New option --verbose to show the changelog of each applied changeset

Line 
1#! /usr/bin/python
2# -*- mode: python; coding: utf-8 -*-
3# :Progetto: vcpx -- Frontend capabilities
4# :Creato:   dom 04 lug 2004 00:40:54 CEST
5# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
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
18from dualwd import DualWorkingDir
19
20STATUS_FILENAME = 'tailor.info'
21LOG_FILENAME = 'tailor.log'
22   
23class TailorizedProject(object):
24    """
25    A TailorizedProject has two main capabilities: it may be bootstrapped
26    from an upstream repository or brought in sync with current upstream
27    revision.
28    """
29   
30    def __init__(self, root, verbose=False):
31        import logging
32        from os import makedirs
33        from os.path import join, exists, split
34
35        self.root = root
36        if not exists(root):
37            makedirs(root)
38
39        self.verbose = verbose
40        self.logger = logging.getLogger('tailor.%s' % split(root)[1])
41        hdlr = logging.FileHandler(join(root, LOG_FILENAME))
42        formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
43        hdlr.setFormatter(formatter)
44        self.logger.addHandler(hdlr) 
45        self.logger.setLevel(logging.INFO)
46
47        self.source_kind = self.target_kind = None
48               
49    def __saveStatus(self):
50        """
51        Save relevant project information in a persistent way.
52        """
53       
54        from os.path import join, exists
55
56        statusfilename = join(self.root, STATUS_FILENAME)
57        f = open(statusfilename, 'w')
58        print >>f, self.source_kind
59        print >>f, self.target_kind       
60        print >>f, self.module       
61        print >>f, self.upstream_repos
62        print >>f, self.upstream_revision
63        f.close()
64       
65    def __loadStatus(self):
66        """
67        Load relevant project information.
68        """
69       
70        from os.path import join, exists
71
72        statusfilename = join(self.root, STATUS_FILENAME)
73        f = open(statusfilename)
74        (srck, dstk,
75         module, upstream_repos, upstream_revision) = f.readlines()
76        self.source_kind = srck[:-1]
77        self.target_kind = dstk[:-1]
78        self.module = module[:-1]
79        self.upstream_repos = upstream_repos[:-1]
80        self.upstream_revision = upstream_revision[:-1]
81        f.close()
82
83    def bootstrap(self, source_kind, target_kind,
84                  repository, module, revision):
85        """
86        Bootstrap a new tailorized module.
87
88        Extract a copy of the `repository` at given `revision` in the `root`
89        directory and initialize a target repository with its content.
90
91        The actual information on the project are stored in a text file.
92        """
93
94        from os.path import split
95       
96        self.logger.info("Bootstrapping '%s'" % (self.root,))
97
98        dwd = DualWorkingDir(source_kind, target_kind)
99        self.logger.info("getting %s revision '%s' of '%s' from '%s'" % (
100            source_kind, revision, module, repository))
101        actual = dwd.checkoutUpstreamRevision(self.root, repository,
102                                              module, revision,
103                                              logger=self.logger)
104
105        self.logger.info("initializing %s shadow" % target_kind)
106       
107        # the above machinery checked out a copy under of the wc
108        # in the directory named as the last component of the module's name
109
110        dwd.initializeNewWorkingDir(self.root, repository,
111                                    split(module)[1], actual)
112
113        self.source_kind = source_kind
114        self.target_kind = target_kind
115        self.upstream_repos = repository
116        self.module = module       
117        self.upstream_revision = actual
118
119        self.__saveStatus()
120
121        self.logger.info("Bootstrap completed")
122
123    def applied(self, root, changeset):
124        """
125        Save current status.
126        """
127
128        self.upstream_revision = changeset.revision
129        self.__saveStatus()
130        if self.verbose:
131            print "# Applied changeset %s" % changeset.revision
132            print changeset.log
133
134    def update(self, single_commit, concatenate_logs):
135        """
136        Update an existing tailorized project.
137
138        Fetch the upstream changesets and apply them to the working copy.
139        Use the information stored in the `tailor.info` file to ask just
140        the new changeset since last bootstrap/synchronization.
141        """
142       
143        from os.path import join, split
144       
145        self.__loadStatus()
146
147        proj = join(self.root, split(self.module)[1])
148        self.logger.info("Updating '%s' from revision '%s'" % (
149            self.module, self.upstream_revision))
150
151        if self.verbose:
152            print "\nUpdating '%s' from revision '%s'" % (self.module,
153                                                          self.upstream_revision)
154       
155        dwd = DualWorkingDir(self.source_kind, self.target_kind)
156        changesets = dwd.getUpstreamChangesets(proj, self.upstream_revision)
157
158        nchanges = len(changesets)
159        if nchanges:
160            if self.verbose:
161                print "Applying %d upstream changesets" % nchanges
162               
163            l,c = dwd.applyUpstreamChangesets(proj, changesets,
164                                              applied=self.applied,
165                                              logger=self.logger,
166                                              delayed_commit=single_commit)
167            if l:
168                if single_commit:
169                    dwd.commitDelayedChangesets(proj, concatenate_logs)
170
171                self.logger.info("Update completed, now at revision '%s'" % (
172                    self.upstream_revision,))
173        else:
174            self.logger.info("Update completed with no upstream changes")
175
176
177from optparse import OptionParser, OptionError, OptionGroup, make_option
178
179GENERAL_OPTIONS = [
180    make_option("-D", "--debug", dest="debug",
181                action="store_true", default=False,
182                help="Print each executed command."),
183    make_option("-v", "--verbose", dest="verbose",
184                action="store_true", default=False,
185                help="Be verbose, echoing the changelog of each applied "
186                     "changeset to stdout."),
187]   
188
189UPDATE_OPTIONS = [
190    make_option("--update", action="store_true", default=True,
191                help="Update the given repositories, fetching upstream "
192                     "changesets, applying and re-registering each one. "
193                     "This is the default behaviour."),
194    make_option("-S", "--single-commit", action="store_true", default=False,
195                help="Do a single, final commit on the target VC, effectively "
196                     "grouping together all upstream changeset into a single "
197                     "one, from the target VC point of view."),
198    make_option("-C", "--concatenate-logs", action="store_true", default=False,
199                help="With --single-commit, concatenate each changeset "
200                     "message log to the final changelog, instead of just "
201                     "the name of the patch."),
202]
203
204BOOTSTRAP_OPTIONS = [
205    make_option("-b", "--bootstrap", action="store_true", default=False,
206                help="Bootstrap mode, that is the initial copy of the "
207                     "upstream tree, given as an URI (see -R) and maybe "
208                     "a revision (-r).  This overrides --update."),
209    make_option("-s", "--source-kind", dest="source_kind", metavar="VC-KIND",
210                help="Select the backend for the upstream source "
211                     "version control VC-KIND. Default is 'cvs'.",
212                default="cvs"),
213    make_option("-t", "--target-kind", dest="target_kind", metavar="VC-KIND",
214                help="Select VC-KIND as backend for the shadow repository, "
215                     "with 'darcs' as default.",
216                default="darcs"),
217    make_option("-R", "--repository", dest="repository", metavar="REPOS",
218                help="Specify the upstream repository, from where bootstrap "
219                     "will checkout the module.  REPOS syntax depends on "
220                     "the source version control kind."),
221    make_option("-m", "--module", dest="module", metavar="MODULE",
222                help="Specify the module to checkout at bootstrap time."),
223    make_option("-r", "--revision", dest="revision", metavar="REV",
224                help="Specify the revision bootstrap should checkout.  REV "
225                     "must be a valid 'name' for a revision in the upstream "
226                     "version control kind.  For CVS it may be a tag/branch. "
227                     "'HEAD', the default, means the latest version in all "
228                     "backends.",
229                default="HEAD"),
230]
231
232class ExistingProjectError(Exception):
233    """
234    Raised when, in bootstrap mode, the directory for the project is already
235    there.
236    """
237
238class UnknownProjectError(Exception):
239    """
240    Raised when, in normal mode, the directory for the project does not
241    exist.
242    """
243
244class ProjectNotTailored(Exception):
245    """
246    Raised when trying to do something on a project that has not been
247    tailored.
248    """
249   
250def main():
251    """
252    Script entry point.
253
254    Parse the command line options and arguments, and for each
255    specified working copy directory (the current working directory by
256    default) execute the tailorization steps.
257    """
258   
259    from os import getcwd, chdir
260    from os.path import abspath, exists, join, split
261    from shwrap import SystemCommand
262   
263    parser = OptionParser(usage='%prog [options] [project ...]',
264                          option_list=GENERAL_OPTIONS)
265   
266    bsoptions = OptionGroup(parser, "Bootstrap options")
267    bsoptions.add_options(BOOTSTRAP_OPTIONS)
268
269    upoptions = OptionGroup(parser, "Update options")
270    upoptions.add_options(UPDATE_OPTIONS)
271   
272    parser.add_option_group(bsoptions)
273    parser.add_option_group(upoptions)
274   
275    options, args = parser.parse_args()
276   
277    SystemCommand.VERBOSE = options.debug
278   
279    base = getcwd()
280   
281    if len(args) == 0:
282        args.append(base)
283       
284    while args:
285        chdir(base)
286       
287        proj = args.pop(0)
288        root = abspath(proj)
289
290        if options.bootstrap:
291            if exists(join(root, STATUS_FILENAME)):
292                raise ExistingProjectError(
293                    "Project %r cannot be bootstrapped twice" % proj)
294           
295            if not options.repository:
296                raise OptionError('Need a repository to bootstrap %r' % proj)
297        else:
298            if not exists(proj):
299                raise UnknownProjectError("Project %r does not exist" % proj)
300           
301            if not exists(join(root, STATUS_FILENAME)):
302                raise UnknownProjectError(
303                    "%r is not a tailorized project" % proj)
304           
305        tailored = TailorizedProject(root, options.verbose)
306
307        if options.bootstrap:
308            tailored.bootstrap(options.source_kind, options.target_kind,
309                               options.repository,
310                               options.module,
311                               options.revision)
312        elif options.update:
313            tailored.update(options.single_commit, options.concatenate_logs)
314
Note: See TracBrowser for help on using the repository browser.