source: tailor/vcpx/target.py @ 607

Revision 607, 9.1 KB checked in by lele@…, 8 years ago (diff)

Renamed initializeNewWorkingDir() to importFirstRevision()

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Syncable targets
3# :Creato:   ven 04 giu 2004 00:27:07 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
5# :Licenza:  GNU General Public License
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
16from workdir import WorkingDir
17
18HOST = socket.getfqdn()
19AUTHOR = "tailor"
20BOOTSTRAP_PATCHNAME = 'Tailorization'
21BOOTSTRAP_CHANGELOG = """\
22Import of the upstream sources from
23
24  Repository: %(source_repository)s%(source_module)s
25  Revision:   %(revision)s
26"""
27
28class TargetInitializationFailure(Exception):
29    "Failure initializing the target VCS"
30
31class SyncronizableTargetWorkingDir(WorkingDir):
32    """
33    This is an abstract working dir usable as a *shadow* of another
34    kind of VC, sharing the same working directory.
35
36    Most interesting entry points are:
37
38    replayChangeset
39        to replay an already applied changeset, to mimic the actions
40        performed by the upstream VC system on the tree such as
41        renames, deletions and adds.  This is an useful argument to
42        feed as ``replay`` to ``applyUpstreamChangesets``
43
44    importFirstRevision
45        to initialize a pristine working directory tree under this VC
46        system, possibly extracted under a different kind of VC
47
48    Subclasses MUST override at least the _underscoredMethods.
49    """
50
51    PATCH_NAME_FORMAT = 'Tailorized "%(revision)s"'
52    """
53    The format string used to compute the patch name, used by underlying VCS.
54    """
55
56    REMOVE_FIRST_LOG_LINE = False
57    """
58    When true, remove the first line from the upstream changelog.
59    """
60
61    def replayChangeset(self, changeset):
62        """
63        Do whatever is needed to replay the changes under the target
64        VC, to register the already applied (under the other VC)
65        changeset.
66        """
67
68        try:
69            self._replayChangeset(changeset)
70        except:
71            self.log_error(str(changeset), exc=True)
72            raise
73
74        if changeset.log == '':
75            firstlogline = 'Empty log message'
76            remaininglog = ''
77        else:
78            loglines = changeset.log.split('\n')
79            if len(loglines)>1:
80                firstlogline = loglines[0]
81                remaininglog = '\n'.join(loglines[1:])
82            else:
83                firstlogline = changeset.log
84                remaininglog = ''
85
86        patchname = self.PATCH_NAME_FORMAT % {
87            'revision': changeset.revision,
88            'author': changeset.author,
89            'date': changeset.date,
90            'firstlogline': firstlogline,
91            'remaininglog': remaininglog}
92        if self.REMOVE_FIRST_LOG_LINE:
93            changelog = remaininglog
94        else:
95            changelog = changeset.log
96        entries = self._getCommitEntries(changeset)
97        self._commit(changeset.date, changeset.author,
98                     patchname, changelog, entries)
99
100    def _getCommitEntries(self, changeset):
101        """
102        Extract the names of the entries for the commit phase.
103        """
104
105        return [e.name for e in changeset.entries]
106
107    def _replayChangeset(self, changeset):
108        """
109        Replicate the actions performed by the changeset on the tree of
110        files.
111        """
112
113        from os.path import join, isdir
114
115        added = changeset.addedEntries()
116        renamed = changeset.renamedEntries()
117        removed = changeset.removedEntries()
118
119        # Sort added entries, to be sure that /root/addedDir/ comes
120        # before /root/addedDir/addedSubdir
121        added.sort(lambda x,y: cmp(x.name, y.name))
122
123        # Sort removes in reverse order, to delete directories after
124        # their contents.
125        removed.sort(lambda x,y: cmp(y.name, x.name))
126
127        # Replay the actions
128
129        if renamed: self._renameEntries(renamed)
130        if removed: self._removeEntries(removed)
131        if added: self._addEntries(added)
132
133        # Finally, deal with "copied" directories. The simple way is
134        # executing an _addSubtree on each of them, evenif this may
135        # cause "warnings" on items just moved/added above...
136
137        while added:
138            subdir = added.pop(0).name
139            if isdir(join(self.basedir, subdir)):
140                self._addSubtree(subdir)
141                added = [e for e in added if not e.name.startswith(subdir)]
142
143    def _addEntries(self, entries):
144        """
145        Add a sequence of entries
146        """
147
148        self._addPathnames([e.name for e in entries])
149
150    def _addPathnames(self, names):
151        """
152        Add some new filesystem objects.
153        """
154
155        raise "%s should override this method" % self.__class__
156
157    def _addSubtree(self, subdir):
158        """
159        Add a whole subtree.
160
161        This implementation crawl down the whole subtree, adding
162        entries (subdirs, skipping the usual VC-specific control
163        directories such as ``.svn``, ``_darcs`` or ``CVS``, and
164        files).
165
166        Subclasses may use a better way, if the backend implements
167        a recursive add that skips the various metadata directories.
168        """
169
170        from os.path import join
171        from os import walk
172        from dualwd import IGNORED_METADIRS
173
174        exclude = []
175
176        if self.state_file.filename.startswith(self.basedir):
177            exclude.append(self.state_file.filename[len(self.basedir)+1:])
178
179        if self.logfile.startswith(self.basedir):
180            exclude.append(self.logfile[len(self.basedir)+1:])
181
182        if subdir and subdir<>'.':
183            self._addPathnames([subdir])
184
185        for dir, subdirs, files in walk(join(self.basedir, subdir)):
186            for excd in IGNORED_METADIRS:
187                if excd in subdirs:
188                    subdirs.remove(excd)
189
190            for excf in exclude:
191                if excf in files:
192                    files.remove(excf)
193
194            if subdirs or files:
195                self._addPathnames([join(dir, df)[len(self.basedir)+1:]
196                                    for df in subdirs + files])
197
198    def _commit(self, date, author, patchname, changelog=None, entries=None):
199        """
200        Commit the changeset.
201        """
202
203        raise "%s should override this method" % self.__class__
204
205    def _removeEntries(self, entries):
206        """
207        Remove a sequence of entries.
208        """
209
210        self._removePathnames([e.name for e in entries])
211
212    def _removePathnames(self, names):
213        """
214        Remove some filesystem object.
215        """
216
217        raise "%s should override this method" % self.__class__
218
219    def _renameEntries(self, entries):
220        """
221        Rename a sequence of entries, adding all the parent directories
222        of each entry.
223        """
224
225        from os.path import split
226
227        added = []
228        for e in entries:
229            parents = []
230            parent = split(e.name)[0]
231            while parent:
232                if not parent in added:
233                    parents.append(parent)
234                    added.append(parent)
235                parent = split(parent)[0]
236            if parents:
237                parents.reverse()
238                self._addPathnames(parents)
239
240            self._renamePathname(e.old_name, e.name)
241
242    def _renamePathname(self, oldname, newname):
243        """
244        Rename a filesystem object to some other name/location.
245        """
246
247        raise "%s should override this method" % self.__class__
248
249    def prepareWorkingDirectory(self, source_repo):
250        """
251        Do anything required to setup the hosting working directory.
252        """
253
254        self._prepareTargetRepository(source_repo)
255        self._prepareWorkingDirectory(source_repo)
256
257    def _prepareTargetRepository(self, source_repo):
258        """
259        Possibly create the repository, when overriden by subclasses.
260        """
261
262    def _prepareWorkingDirectory(self, source_repo):
263        """
264        Possibly checkout a working copy of the target VC, that will host the
265        upstream source tree, when overriden by subclasses.
266        """
267
268    def importFirstRevision(self, source_repo, changeset, initial):
269        """
270        Initialize a new working directory, just extracted from
271        some other VC system, importing everything's there.
272        """
273
274        self._initializeWorkingDir()
275        revision = changeset.revision
276        source_repository = source_repo.repository
277        source_module = source_repo.module or ''
278        if initial:
279            author = changeset.author
280            patchname = changeset.log
281            log = None
282        else:
283            author = "%s@%s" % (AUTHOR, HOST)
284            patchname = BOOTSTRAP_PATCHNAME
285            log = BOOTSTRAP_CHANGELOG % locals()
286        self._commit(changeset.date, author, patchname, log)
287
288    def _initializeWorkingDir(self):
289        """
290        Assuming the ``basedir`` directory contains a working copy ``module``
291        extracted from some VC repository, add it and all its content
292        to the target repository.
293
294        This implementation recursively add every file in the subtree.
295        Subclasses should override this method doing whatever is
296        appropriate for the backend.
297        """
298
299        self._addSubtree('.')
Note: See TracBrowser for help on using the repository browser.