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

Revision 1682, 15.6 KB checked in by lele@…, 2 years ago (diff)

get_apparent_author() was deprecated 1.13, dropped its usage

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Bazaar 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.
13"""
14
15__docformat__ = 'reStructuredText'
16
17
18from sys import version_info
19assert version_info >= (2,4), "Bazaar backend requires Python 2.4"
20del version_info
21
22from bzrlib import errors
23from bzrlib.branch import Branch
24from bzrlib.bzrdir import BzrDir
25from bzrlib.errors import NoSuchRevision, TagsNotSupported
26from bzrlib.missing import find_unmerged
27from bzrlib.osutils import normpath, pathjoin
28from bzrlib.plugin import load_plugins
29
30from vcpx.changes import Changeset, ChangesetEntry
31from vcpx.repository import Repository
32from vcpx.source import UpdatableSourceWorkingDir, ChangesetApplicationFailure
33from vcpx.target import SynchronizableTargetWorkingDir
34from vcpx.workdir import WorkingDir
35
36
37class BzrChangeset(Changeset):
38    """
39    Manage the particular reordering of the entries.
40
41    Apparently TreeDelta doesn't expose the entries in a sensible order,
42    they are grouped by kind.
43    """
44
45    def __init__(self, revision, date, author, log, entries=None, **other):
46        """
47        Initialize a new BzrChangeset, inserting the entries in a sensible order.
48        """
49
50        from os.path import split, join
51
52        super(BzrChangeset, self).__init__(revision, date, author, log, entries=None, **other)
53        if entries is not None:
54            for e in entries:
55                self.addEntry(e, revision)
56
57            # Adjust old_name on renamed entries: bzr tell us the *original*
58            # name of the rename...
59            # Consider this:
60            #
61            #     $ bzr mv newnamedir/subdir/a newnamedir/subdir/b
62            #     newnamedir/subdir/a => newnamedir/subdir/b
63            #     $ bzr mv newnamedir/subdir newnamedir/newsubdir
64            #     newnamedir/subdir => newnamedir/newsubdir
65            #     $ bzr mv newnamedir dir
66            #     newnamedir => dir
67            #     $ bzr st
68            #     renamed:
69            #       newnamedir => dir
70            #       newnamedir/subdir => dir/newsubdir
71            #       newnamedir/subdir/a => dir/newsubdir/b
72
73            renames = {}
74            for e in self.entries:
75                if e.action_kind == e.RENAMED:
76                    renames[e.old_name] = e.name
77                    d,f = split(e.old_name)
78                    while d:
79                        if d in renames:
80                            e.old_name = join(renames[d], e.old_name[len(d)+1:])
81                            break
82                        d,f = split(d)
83
84    def addEntry(self, entry, revision):
85        """
86        Fixup the ordering of the entries, by giving precedence to directories
87        """
88
89        if entry.action_kind in (entry.ADDED, entry.RENAMED) and entry.is_directory:
90            dirname = entry.name + '/' # does bzr on windows use this too?
91            for i,e in enumerate(self.entries):
92                if e.name.startswith(dirname):
93                    self.entries.insert(i, entry)
94                    return
95        elif entry.action_kind == entry.DELETED:
96            for i,e in enumerate(self.entries):
97                if e.action_kind == e.RENAMED and e.name == entry.name:
98                    # This is the following case:
99                    #  $ bzr rm A
100                    #  $ bzr mv B A
101                    self.entries.insert(i, entry)
102                    return
103                elif (e.action_kind == e.DELETED
104                      and e.is_directory
105                      and entry.name.startswith(e.name)):
106                    # Remove dir contents before dir itself
107                    self.entries.insert(i, entry)
108                    return
109                elif (e.action_kind == e.ADDED and e.name == entry.name):
110                    # put replacement (rm+add) in the right order
111                    self.entries.insert(i, entry)
112                    return
113
114        self.entries.append(entry)
115
116
117class BzrRepository(Repository):
118    METADIR = '.bzr'
119
120    def _load(self, project):
121        Repository._load(self, project)
122        ppath = project.config.get(self.name, 'python-path')
123        if ppath:
124            from sys import path
125
126            if ppath not in path:
127                path.insert(0, ppath)
128
129    def create(self):
130        """
131        Create a branch with a working tree at the base directory. If the base
132        directory is inside a Bazaar style "shared repository", it will use
133        that to create a branch and working tree (make sure it allows working
134        trees).
135        """
136
137        self.log.info('Initializing new repository in %r...', self.basedir)
138        try:
139            bzrdir = BzrDir.open(self.basedir)
140        except errors.NotBranchError:
141            # really a NotBzrDir error...
142            branch = BzrDir.create_branch_convenience(self.basedir, force_new_tree=True)
143            wtree = branch.bzrdir.open_workingtree()
144        else:
145            bzrdir.create_branch()
146            wtree = bzrdir.create_workingtree()
147
148        return wtree
149
150
151class BzrWorkingDir(UpdatableSourceWorkingDir, SynchronizableTargetWorkingDir):
152    def __init__(self, repository):
153        from os.path import split
154        from bzrlib import version_info, IGNORE_FILENAME
155
156        if version_info > (0,9):
157            from bzrlib.ignores import add_runtime_ignores, parse_ignore_file
158        else:
159            from bzrlib import DEFAULT_IGNORE
160
161        WorkingDir.__init__(self, repository)
162        # TODO: check if there is a "repository" in the configuration,
163        # and use it as a bzr repository
164        self.ignored = []
165        self._working_tree = None
166
167        # The bzr repository may have some plugins that needs to be activated
168        load_plugins()
169
170        try:
171            bzrdir = BzrDir.open(self.repository.basedir)
172            wt = self._working_tree = bzrdir.open_workingtree()
173
174            # read .bzrignore for _addSubtree()
175            if wt.has_filename(IGNORE_FILENAME):
176                f = wt.get_file_byname(IGNORE_FILENAME)
177                if version_info > (0,9):
178                    self.ignored.extend(parse_ignore_file(f))
179                else:
180                    self.ignored.extend([ line.rstrip("\n\r") for line in f.readlines() ])
181                f.close()
182        except (errors.NotBranchError, errors.NoWorkingTree):
183            pass
184
185        # Omit our own log...
186        logfile = self.repository.projectref().logfile
187        dir, file = split(logfile)
188        if dir == self.repository.basedir:
189            self.ignored.append(file)
190
191        # ... and state file
192        sfname = self.repository.projectref().state_file.filename
193        dir, file = split(sfname)
194        if dir == self.repository.basedir:
195            self.ignored.append(file)
196            self.ignored.append(file+'.old')
197            self.ignored.append(file+'.journal')
198
199        if version_info > (0,9):
200            add_runtime_ignores(self.ignored)
201        else:
202            DEFAULT_IGNORE.extend(self.ignored)
203
204
205    #############################
206    ## UpdatableSourceWorkingDir
207
208    def _changesetFromRevision(self, branch, revision_id):
209        """
210        Generate changeset for the given Bzr revision
211        """
212        from datetime import datetime
213        from vcpx.tzinfo import FixedOffset, UTC
214
215        revision = branch.repository.get_revision(revision_id)
216        deltatree = branch.get_revision_delta(branch.revision_id_to_revno(revision_id))
217        entries = []
218
219        for delta in deltatree.renamed:
220            e = ChangesetEntry(delta[1])
221            e.action_kind = ChangesetEntry.RENAMED
222            e.old_name = delta[0]
223            e.is_directory = delta[3] == 'directory'
224            entries.append(e)
225
226        for delta in deltatree.added:
227            e = ChangesetEntry(delta[0])
228            e.action_kind = ChangesetEntry.ADDED
229            e.is_directory = delta[2] == 'directory'
230            entries.append(e)
231
232        for delta in deltatree.removed:
233            e = ChangesetEntry(delta[0])
234            e.action_kind = ChangesetEntry.DELETED
235            e.is_directory = delta[2] == 'directory'
236            entries.append(e)
237
238        for delta in deltatree.modified:
239            e = ChangesetEntry(delta[0])
240            e.action_kind = ChangesetEntry.UPDATED
241            entries.append(e)
242
243        if revision.timezone is not None:
244            timezone = FixedOffset(revision.timezone / 60)
245        else:
246            timezone = UTC
247
248        timestamp = datetime.fromtimestamp(revision.timestamp, timezone)
249        author = revision.get_apparent_authors()[0]
250
251        return BzrChangeset(revision.revision_id,
252                            timestamp,
253                            author,
254                            revision.message,
255                            entries)
256
257    def _getUpstreamChangesets(self, sincerev):
258        """
259        See what other revisions exist upstream and return them
260        """
261
262        from bzrlib import version_info
263
264        parent_branch = Branch.open(self.repository.repository)
265
266        branch = self._working_tree.branch
267        branch.lock_read()
268        try:
269            parent_branch.lock_read()
270            try:
271                if version_info > (1, 6):
272                    revisions = find_unmerged(branch, parent_branch, 'remote')[1]
273                else:
274                    revisions = find_unmerged(branch, parent_branch)[1]
275
276                self.log.info("Collecting %d missing changesets", len(revisions))
277
278                for id, revision in revisions:
279                    yield self._changesetFromRevision(parent_branch, revision)
280            except:
281                parent_branch.unlock()
282                raise
283            parent_branch.unlock()
284        except:
285            branch.unlock()
286            raise
287        branch.unlock()
288
289        self.log.info("Fetching concrete changesets")
290        branch.lock_write()
291        try:
292            branch.fetch(parent_branch)
293        finally:
294            branch.unlock()
295
296    def _applyChangeset(self, changeset):
297        """
298        Apply the given changeset to the working tree
299        """
300        parent_branch = BzrDir.open(self.repository.repository).open_branch()
301        self._working_tree.lock_write()
302        try:
303            count = self._working_tree.pull(parent_branch,
304                                            stop_revision=changeset.revision)
305            # XXX: this does not seem to return a true value on conflicts!
306            conflicts = self._working_tree.update()
307        finally:
308            self._working_tree.unlock()
309        try:
310            pulled_revnos = count.new_revno - count.old_revno
311        except AttributeError:
312            # Prior to 0.15 pull returned a simple integer instead of a result object
313            pulled_revnos = count
314        self.log.info('Updated to %r, applied %d changesets', changeset.revision, count)
315        if conflicts:
316            # No conflict handling yet
317            raise ChangesetApplicationFailure('Unsupported: conflicts')
318        return []
319
320    def _checkoutUpstreamRevision(self, revision):
321        """
322        Initial checkout of upstream branch, equivalent of 'bzr branch -r',
323        and return the last changeset.
324        """
325
326        from os.path import join, exists
327
328        if exists(join(self.repository.basedir, '.bzr')):
329            bzrdir = BzrDir.open(self.repository.basedir)
330            branch = bzrdir.open_branch()
331            self._working_tree = bzrdir.open_workingtree()
332            revid = self._working_tree.last_revision()
333            return self._changesetFromRevision(branch, revid)
334        else:
335            parent_bzrdir = BzrDir.open(self.repository.repository)
336            parent_branch = parent_bzrdir.open_branch()
337
338            if revision == "INITIAL":
339                try:
340                    revid = parent_branch.get_rev_id(1)
341                except NoSuchRevision:
342                    return None
343            elif revision == "HEAD":
344                revid = None
345            else:
346                revid = revision
347
348            self.log.info('Extracting %r out of %r in %r...',
349                          revid, parent_bzrdir.root_transport.base,
350                          self.repository.basedir)
351            bzrdir = parent_bzrdir.sprout(self.repository.basedir, revid)
352            self._working_tree = bzrdir.open_workingtree()
353
354            return self._changesetFromRevision(parent_branch, revid)
355
356    #################################
357    ## SynchronizableTargetWorkingDir
358
359    def _addPathnames(self, names):
360        if len(names):
361            names = [ pathjoin(self.repository.basedir, n) for n in names ]
362            self._working_tree.smart_add(names, recurse=False)
363
364    def _addSubtree(self, subdir):
365        subdir = pathjoin(self.repository.basedir, subdir)
366        added, ignored = self._working_tree.smart_add([subdir], recurse=True)
367
368        from vcpx.dualwd import IGNORED_METADIRS
369
370        for meta in IGNORED_METADIRS + self.ignored:
371            if ignored.has_key(meta):
372                del ignored[meta]
373
374        if len(ignored):
375            f = []
376            map(f.extend, ignored.values())
377            self._addPathnames(f)
378
379    def _commit(self, date, author, patchname, changelog=None, entries=None,
380                tags = [], isinitialcommit = False):
381        """
382        Commit the changeset.
383        """
384        from calendar import timegm  # like mktime(), but returns UTC timestamp
385        from binascii import hexlify
386        from re import search
387
388        logmessage = []
389        if patchname:
390            logmessage.append(patchname)
391        if changelog:
392            logmessage.append(changelog)
393        if logmessage:
394            self.log.info('Committing %s...', logmessage[0])
395            logmessage = '\n'.join(logmessage)
396        else:
397            self.log.info('Committing...')
398            logmessage = "Empty changelog"
399
400        timestamp = timegm(date.utctimetuple())
401        timezone  = date.utcoffset().seconds + date.utcoffset().days * 24 * 3600
402
403        # Normalize file names
404        if entries:
405            entries = [normpath(entry) for entry in entries]
406
407        self._working_tree.commit(logmessage, committer=author,
408                                  specific_files=entries,
409                                  verbose=self.repository.projectref().verbose,
410                                  timestamp=timestamp, timezone=timezone)
411
412    def _tag(self, tagname, date, author):
413        """
414        Tag the current version, if supported.
415        """
416        branch = self._working_tree.branch
417        try:
418            branch.tags.set_tag(tagname, branch.last_revision())
419        except TagsNotSupported:
420            pass
421
422    def _removePathnames(self, names):
423        """
424        Remove files from the tree.
425        """
426        self.log.info('Removing %s...', ', '.join(names))
427        names.sort(reverse=True) # remove files before the dir they're in
428        self._working_tree.remove(names)
429
430    def _renamePathname(self, oldname, newname):
431        """
432        Rename a file from oldname to newname.
433        """
434        self.log.info('Renaming %r to %r...', oldname, newname)
435        self._working_tree.rename_one(oldname, newname)
436
437    def _prepareTargetRepository(self):
438        from bzrlib import version_info
439        from vcpx.dualwd import IGNORED_METADIRS
440
441        if self._working_tree is None:
442            self._working_tree = self.repository.create()
443
444        if version_info > (0,9):
445            from bzrlib.ignores import add_runtime_ignores
446            add_runtime_ignores(IGNORED_METADIRS)
447        else:
448            from bzrlib import DEFAULT_IGNORE
449            DEFAULT_IGNORE.extend(IGNORED_METADIRS)
Note: See TracBrowser for help on using the repository browser.