source: tailor/vcpx/repository/bzr.py @ 1233

Revision 1233, 10.7 KB checked in by lele@…, 7 years ago (diff)

Reorder the imports as suggested by the bzr policy

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- bazaar-ng support using the bzrlib instead of the frontend
3# :Creato:   Fri Aug 19 01:06:08 CEST 2005
4# :Autore:   Johan Rydberg <jrydberg@gnu.org>
5#            Jelmer Vernooij <jelmer@samba.org>
6#            Lalo Martins <lalo.martins@gmail.com>
7#            Olaf Conradi <olaf@conradi.org>
8# :Licenza:  GNU General Public License
9#
10
11"""
12This module implements the backends for Bazaar-NG.
13"""
14
15__docformat__ = 'reStructuredText'
16
17
18from sys import version_info
19assert version_info >= (2,4), "Bazaar-NG backend requires Python 2.4"
20del version_info
21
22from bzrlib import IGNORE_FILENAME, DEFAULT_IGNORE, errors
23from bzrlib.add import smart_add_tree
24from bzrlib.bzrdir import BzrDir
25from bzrlib.delta import compare_trees
26from bzrlib.osutils import normpath, pathjoin
27
28from vcpx.repository import Repository
29from vcpx.source import UpdatableSourceWorkingDir, ChangesetApplicationFailure
30from vcpx.target import SynchronizableTargetWorkingDir
31from vcpx.workdir import WorkingDir
32
33
34class BzrRepository(Repository):
35    METADIR = '.bzr'
36
37    def _load(self, project):
38        Repository._load(self, project)
39        ppath = project.config.get(self.name, 'python-path')
40        if ppath:
41            from sys import path
42
43            if ppath not in path:
44                path.insert(0, ppath)
45
46    def create(self):
47        """
48        Create a branch with a working tree at the base directory. If the base
49        directory is inside a Bazaar-NG style "shared repository", it will use
50        that to create a branch and working tree (make sure it allows working
51        trees).
52        """
53
54        self.log.info('Initializing new repository in %r...', self.basedir)
55        try:
56            bzrdir = BzrDir.open(self.basedir)
57        except errors.NotBranchError:
58            # really a NotBzrDir error...
59            branch = BzrDir.create_branch_convenience(self.basedir, force_new_tree=True)
60            wtree = branch.bzrdir.open_workingtree()
61        else:
62            bzrdir.create_branch()
63            wtree = bzrdir.create_workingtree()
64
65        return wtree
66
67
68class BzrWorkingDir(UpdatableSourceWorkingDir, SynchronizableTargetWorkingDir):
69    def __init__(self, repository):
70        WorkingDir.__init__(self, repository)
71        # TODO: check if there is a "repository" in the configuration,
72        # and use it as a bzr repository
73        self.ignored = []
74        self._working_tree = None
75        try:
76            bzrdir = BzrDir.open(self.repository.basedir)
77            wt = self._working_tree = bzrdir.open_workingtree()
78
79            # read .bzrignore for _addSubtree()
80            if wt.has_filename(IGNORE_FILENAME):
81                f = wt.get_file_byname(IGNORE_FILENAME)
82                self.ignored.extend([ line.rstrip("\n\r") for line in f.readlines() ])
83        except errors.NotBranchError, errors.NoWorkingTree:
84            pass
85
86        from os.path import split
87
88        # Omit our own log...
89        logfile = self.repository.projectref().logfile
90        dir, file = split(logfile)
91        if dir == self.repository.basedir:
92            self.ignored.append(file)
93
94        # ... and state file
95        sfname = self.repository.projectref().state_file.filename
96        dir, file = split(sfname)
97        if dir == self.repository.basedir:
98            self.ignored.append(file)
99            self.ignored.append(file+'.old')
100            self.ignored.append(file+'.journal')
101
102        DEFAULT_IGNORE.extend(self.ignored)
103
104
105    #############################
106    ## UpdatableSourceWorkingDir
107
108    def _changesetFromRevision(self, branch, revision_id):
109        """
110        Generate changeset for the given Bzr revision
111        """
112        from datetime import datetime
113        from vcpx.changes import ChangesetEntry, Changeset
114        from vcpx.tzinfo import FixedOffset, UTC
115
116        revision = branch.repository.get_revision(revision_id)
117        deltatree = branch.get_revision_delta(branch.revision_id_to_revno(revision_id))
118        entries = []
119
120        for delta in deltatree.added:
121            e = ChangesetEntry(delta[0])
122            e.action_kind = ChangesetEntry.ADDED
123            entries.append(e)
124
125        for delta in deltatree.removed:
126            e = ChangesetEntry(delta[0])
127            e.action_kind = ChangesetEntry.DELETED
128            entries.append(e)
129
130        for delta in deltatree.renamed:
131            e = ChangesetEntry(delta[1])
132            e.action_kind = ChangesetEntry.RENAMED
133            e.old_name = delta[0]
134            entries.append(e)
135
136        for delta in deltatree.modified:
137            e = ChangesetEntry(delta[0])
138            e.action_kind = ChangesetEntry.UPDATED
139            entries.append(e)
140
141        if revision.timezone is not None:
142            timezone = FixedOffset(revision.timezone / 60)
143        else:
144            timezone = UTC
145
146        return Changeset(revision.revision_id,
147                         datetime.fromtimestamp(revision.timestamp, timezone),
148                         revision.committer,
149                         revision.message,
150                         entries)
151
152    def _getUpstreamChangesets(self, sincerev):
153        """
154        See what other revisions exist upstream and return them
155        """
156        parent_branch = BzrDir.open(self.repository.repository).open_branch()
157        branch = self._working_tree.branch
158        revisions = branch.missing_revisions(parent_branch)
159        branch.fetch(parent_branch)
160
161        for revision_id in revisions:
162            yield self._changesetFromRevision(parent_branch, revision_id)
163
164    def _applyChangeset(self, changeset):
165        """
166        Apply the given changeset to the working tree
167        """
168        parent_branch = BzrDir.open(self.repository.repository).open_branch()
169        self._working_tree.lock_write()
170        self.log.info('Updating to %r', changeset.revision)
171        try:
172            count = self._working_tree.pull(parent_branch,
173                                            stop_revision=changeset.revision)
174            conflicts = self._working_tree.update()
175        finally:
176            self._working_tree.unlock()
177        self.log.debug("%s updated to %s",
178                       ', '.join([e.name for e in changeset.entries]),
179                       changeset.revision)
180        if (count != 1) or conflicts:
181            raise ChangesetApplicationFailure('unknown reason')
182        return [] # No conflict handling yet
183
184    def _checkoutUpstreamRevision(self, revision):
185        """
186        Initial checkout of upstream branch, equivalent of 'bzr branch -r',
187        and return the last changeset.
188        """
189        parent_bzrdir = BzrDir.open(self.repository.repository)
190        parent_branch = parent_bzrdir.open_branch()
191
192        if revision == "INITIAL":
193            revid = parent_branch.get_rev_id(1)
194        elif revision == "HEAD":
195            revid = None
196        else:
197            revid = revision
198
199        self.log.info('Extracting %r out of %r in %r...',
200                      revid, parent_bzrdir.root_transport.base, self.repository.basedir)
201        bzrdir = parent_bzrdir.sprout(self.repository.basedir, revid)
202        self._working_tree = bzrdir.open_workingtree()
203
204        return self._changesetFromRevision(parent_branch, revid)
205
206    #################################
207    ## SynchronizableTargetWorkingDir
208
209    def _addPathnames(self, names):
210        if len(names):
211            names = [ pathjoin(self.repository.basedir, n) for n in names ]
212            smart_add_tree(self._working_tree, names, recurse=False)
213
214    def _addSubtree(self, subdir):
215        subdir = pathjoin(self.repository.basedir, subdir)
216        added, ignored = smart_add_tree(self._working_tree, [subdir], recurse=True)
217
218        from vcpx.dualwd import IGNORED_METADIRS
219
220        for meta in IGNORED_METADIRS + self.ignored:
221            if ignored.has_key(meta):
222                del ignored[meta]
223
224        if len(ignored):
225            f = []
226            map(f.extend, ignored.values())
227            self._addPathnames(f)
228
229    def _commit(self, date, author, patchname, changelog=None, entries=None):
230        """
231        Commit the changeset.
232        """
233        from calendar import timegm  # like mktime(), but returns UTC timestamp
234        from binascii import hexlify
235        from re import search
236        from bzrlib.osutils import compact_date, rand_bytes
237
238        logmessage = []
239        if patchname:
240            logmessage.append(patchname)
241        if changelog:
242            logmessage.append(changelog)
243        if logmessage:
244            self.log.info('Committing %r...', logmessage[0])
245            logmessage = '\n'.join(logmessage)
246        else:
247            self.log.info('Committing...')
248            logmessage = "Empty changelog"
249
250        timestamp = timegm(date.utctimetuple())
251        timezone  = date.utcoffset().seconds + date.utcoffset().days * 24 * 3600
252
253        # Guess sane email address
254        email = search("<(.*@.*)>", author)
255        if email:
256            email = email.group(1)
257        else:
258            email = author
259        # Remove whitespace
260        email = ''.join(email.split())
261
262        # Normalize file names
263        if entries:
264            entries = [normpath(entry) for entry in entries]
265
266        revision_id = "%s-%s-%s" % (email, compact_date(timestamp),
267                                    hexlify(rand_bytes(8)))
268        self._working_tree.commit(logmessage, committer=author,
269                                  specific_files=entries, rev_id=revision_id,
270                                  verbose=self.repository.projectref().verbose,
271                                  timestamp=timestamp, timezone=timezone)
272
273    def _removePathnames(self, names):
274        """
275        Remove files from the tree.
276        """
277        self.log.info('Removing %s...', ', '.join(names))
278        names.sort(reverse=True) # remove files before the dir they're in
279        self._working_tree.remove(names)
280
281    def _renamePathname(self, oldname, newname):
282        """
283        Rename a file from oldname to newname.
284        """
285        from os import rename
286        from os.path import join, exists
287
288        # bzr does the rename itself as well
289        unmoved = False
290        oldpath = join(self.repository.basedir, oldname)
291        newpath = join(self.repository.basedir, newname)
292        if not exists(oldpath):
293            try:
294                rename(newpath, oldpath)
295            except OSError:
296                self.log.critical('Cannot rename %r back to %r',
297                                  newpath, oldpath)
298                raise
299            unmoved = True
300
301        self.log.info('Renaming %r to %r...', oldname, newname)
302        try:
303            self._working_tree.rename_one(oldname, newname)
304        except:
305            if unmoved:
306                rename(oldpath, newpath)
307            raise
308
309    def _prepareTargetRepository(self):
310        if self._working_tree is None:
311            self._working_tree = self.repository.create()
Note: See TracBrowser for help on using the repository browser.