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

Revision 1281, 11.7 KB checked in by lele@…, 7 years ago (diff)

Fix ticket #75, at least with DisjunctWorkingDirectories
While fixing it with shared basedirs is a tricky business, with disjunct
basedirs it's very simple: first replay the changeset on the target, then
execute rsync to copy updated files.

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