source: tailor/vcpx/target.py @ 202

Revision 202, 8.0 KB checked in by lele@…, 8 years ago (diff)

Do not include 'tailor.log' nor 'tailor.info'
This is questionable: those files come in when using the current directory
as the new container, not a subdirectory as tailor mandated. There should
be an option to move/disable/specify the logfile.

Line 
1#! /usr/bin/python
2# -*- mode: python; coding: utf-8 -*-
3# :Progetto: vcpx -- Syncable targets
4# :Creato:   ven 04 giu 2004 00:27:07 CEST
5# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
6#
7
8"""
9Syncronizable targets are the simplest abstract wrappers around a
10working directory under two different version control systems.
11"""
12
13__docformat__ = 'reStructuredText'
14
15import socket
16
17HOST = socket.getfqdn()
18AUTHOR = "tailor"
19BOOTSTRAP_PATCHNAME = 'Tailorization of %s'
20BOOTSTRAP_CHANGELOG = """\
21Import of the upstream sources from the repository
22
23 %(repository)s
24
25as of revision %(revision)s
26"""
27
28class TargetInitializationFailure(Exception):
29    "Failure initializing the target VCS"
30   
31    pass
32
33class SyncronizableTargetWorkingDir(object):
34    """
35    This is an abstract working dir usable as a *shadow* of another
36    kind of VC, sharing the same working directory.
37
38    Most interesting entry points are:
39
40    replayChangeset
41        to replay an already applied changeset, to mimic the actions
42        performed by the upstream VC system on the tree such as
43        renames, deletions and adds.  This is an useful argument to
44        feed as `replay` to `applyUpstreamChangesets`
45
46    initializeNewWorkingDir
47        to initialize a pristine working directory tree under this VC
48        system, possibly extracted under a different kind of VC
49   
50    Subclasses MUST override at least the _underscoredMethods.
51    """
52
53    def replayChangeset(self, root, module, changeset,
54                        delayed_commit=False, logger=None):
55        """
56        Do whatever is needed to replay the changes under the target
57        VC, to register the already applied (under the other VC)
58        changeset.
59
60        If `delayed_commit` is not True, the changeset is committed
61        to the target VC right after a successful application; otherwise
62        the various information get registered and will be reused later,
63        by commitDelayedChangesets().
64        """
65
66        try:
67            self._replayChangeset(root, changeset, logger)
68        except:
69            if logger: logger.critical(str(changeset))
70            raise
71       
72        if delayed_commit:
73            self.__registerAppliedChangeset(changeset)
74        else:
75            from os.path import split
76
77            remark = '%s: changeset %s' % (module, changeset.revision)
78            changelog = changeset.log
79            entries = [e.name for e in changeset.entries]
80            self._commit(root, changeset.date, changeset.author,
81                         remark, changelog, entries)
82
83    def commitDelayedChangesets(self, root, concatenate_logs=True):
84        """
85        If there are changesets pending to be committed, do a single
86        commit of all changed entries.
87
88        With `concatenate_logs` there's control over the folded
89        changesets message log: if True every changelog is appended in
90        order of application, otherwise it will contain just the name
91        of the patches.
92        """
93
94        from datetime import datetime
95       
96        if not hasattr(self, '_registered_cs'):
97            return
98
99        mindate = maxdate = None
100        combined_entries = {}
101        combined_log = []
102        combined_authors = {}
103        for cs in self._registered_cs:
104            if not mindate or mindate>cs.date:
105                mindate = cs.date
106            if not maxdate or maxdate<cs.date:
107                maxdate = cs.date
108
109            if concatenate_logs:
110                msg = 'changeset %s by %s' % (cs.revision, cs.author)
111                combined_log.append(msg)
112                combined_log.append('=' * len(msg))
113                combined_log.append(cs.log)
114            else:
115                combined_log.append('* changeset %s by %s' % (cs.revision,
116                                                              cs.author))
117            combined_authors[cs.author] = True
118           
119            for e in [e.name for e in cs.entries]:
120                combined_entries[e] = True
121
122        authors = ', '.join(combined_authors.keys())
123        remark = 'Merged %d changesets from %s to %s' % (
124            len(self._registered_cs), mindate, maxdate)
125        changelog = '\n'.join(combined_log)
126        entries = combined_entries.keys()
127        self._commit(root, datetime.now(), authors,
128                         remark, changelog, entries)
129   
130    def _replayChangeset(self, root, changeset, logger):
131        """
132        Replicate the actions performed by the changeset on the tree of
133        files.
134        """
135
136        added = changeset.addedEntries()
137        renamed = changeset.renamedEntries()
138        removed = changeset.removedEntries()
139
140        # Sort entries, to be sure added directories come before their
141        # entries.
142        added.sort(lambda x,y: cmp(x.name, y.name))
143
144        # Likewise, sort removed one, but in reverse order
145        removed.sort(lambda x,y: cmp(y.name, x.name))
146               
147        for e in added:
148            self._addEntry(root, e.name)
149
150        for e in renamed:
151            self._renameEntry(root, e.old_name, e.name)
152
153        for e in removed:
154            self._removeEntry(root, e.name)
155           
156    def __registerAppliedChangeset(self, changeset):
157        """
158        Remember about an already applied but not committed changeset,
159        to be done later.
160        """
161       
162        if not hasattr(self, '_registered_cs'):
163            self._registered_cs = []
164
165        self._registered_cs.append(changeset)
166       
167    def _addEntry(self, root, entry):
168        """
169        Add a new entry.
170        """
171
172        raise "%s should override this method" % self.__class__
173
174    def _commit(self, root, date, author, remark,
175                changelog=None, entries=None):
176        """
177        Commit the changeset.
178        """
179       
180        raise "%s should override this method" % self.__class__
181       
182    def _removeEntry(self, root, entry):
183        """
184        Remove an entry.
185        """
186
187        raise "%s should override this method" % self.__class__
188
189    def _renameEntry(self, root, oldentry, newentry):
190        """
191        Rename an entry.
192        """
193
194        raise "%s should override this method" % self.__class__
195
196    def initializeNewWorkingDir(self, root, repository, module, subdir, revision):
197        """
198        Initialize a new working directory, just extracted from
199        some other VC system, importing everything's there.
200        """
201
202        from datetime import datetime
203
204        now = datetime.now()
205        self._initializeWorkingDir(root, repository, module, subdir)
206        self._commit(root, now, '%s@%s' % (AUTHOR, HOST),
207                     BOOTSTRAP_PATCHNAME % module,
208                     BOOTSTRAP_CHANGELOG % locals(),
209                     entries=[subdir])
210
211    def _initializeWorkingDir(self, root, repository, module, subdir, addentry=None):
212        """
213        Assuming the `root` directory contains a working copy `module`
214        extracted from some VC repository, add it and all its content
215        to the target repository.
216
217        This implementation first runs the given `addentry`
218        *SystemCommand* on the `root` directory, then it walks down
219        the `root` tree executing the same command on each entry
220        excepted the usual VC-specific control directories such as
221        ``.svn``, ``_darcs`` or ``CVS``.
222
223        If this does make sense, subclasses should just call this
224        method with the right `addentry` command.
225        """
226
227        assert addentry, "Subclass should have specified something as addentry"
228       
229        from os.path import split, join
230        from os import walk
231
232        if module:
233            c = addentry(working_dir=root)
234            c(entry=repr(module))
235
236        for dir, subdirs, files in walk(join(root, module or '')):
237            for excd in ['.svn', '_darcs', 'CVS']:
238                if excd in subdirs:
239                    subdirs.remove(excd)
240
241            # Uhm, is this really desiderable?
242            for excf in ['tailor.info', 'tailor.log']:
243                if excf in files:
244                    files.remove(excf)
245                   
246            c = addentry(working_dir=dir)
247            c(entry=' '.join([repr(e) for e in subdirs+files]))
248
Note: See TracBrowser for help on using the repository browser.