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

Revision 1196, 11.2 KB checked in by Adeodato Simo <dato@…>, 7 years ago (diff)

[bzr] when removing files, get files removed before the directory they're in

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