source: tailor/vcpx/bzr.py @ 1049

Revision 1049, 8.7 KB checked in by lele@…, 7 years ago (diff)

Do not add entries inside a moved/copied directory on bzr target
The fix to #24 and indirectly #23 triggered (actually, the test did) a
similar issue solved for Subversion with [840] and [841]. This patch
applies a similar solution for BazaarNG, to prevent addition within a
renamed/copied directory. Specifically, this may happen for example
in a changeset coming from Svn that

  1. copies a directory
  2. replaces (a Subversion "R" event) an item in the target with something else
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# :Licenza:  GNU General Public License
7#
8
9"""
10This module implements the backends for Bazaar-NG.
11"""
12
13__docformat__ = 'reStructuredText'
14
15from source import UpdatableSourceWorkingDir
16from target import SyncronizableTargetWorkingDir, TargetInitializationFailure
17from bzrlib.branch import Branch
18from bzrlib.delta import compare_trees
19
20class BzrWorkingDir(UpdatableSourceWorkingDir, SyncronizableTargetWorkingDir):
21    ## UpdatableSourceWorkingDir
22
23    def _changesetFromRevision(self, parent, revision):
24        """
25        Generate changeset for the given Bzr revision
26        """
27        from changes import ChangesetEntry, Changeset
28        from datetime import datetime
29        r = parent.get_revision(revision)
30
31        deltatree = parent.get_revision_delta(parent.revision_id_to_revno(revision))
32        entries = []
33
34        for delta in deltatree.added:
35            e = ChangesetEntry(delta[0])
36            e.action_kind = ChangesetEntry.ADDED
37            entries.append(e)
38
39        for delta in deltatree.removed:
40            e = ChangesetEntry(delta[0])
41            e.action_kind = ChangesetEntry.DELETED
42            entries.append(e)
43
44        for delta in deltatree.renamed:
45            e = ChangesetEntry(delta[1])
46            e.action_kind = ChangesetEntry.RENAMED
47            e.old_name = delta[0]
48            entries.append(e)
49
50        for delta in deltatree.modified:
51            e = ChangesetEntry(delta[0])
52            e.action_kind = ChangesetEntry.UPDATED
53            entries.append(e)
54
55        return Changeset(r.revision_id,
56                              datetime.fromtimestamp(r.timestamp),
57                              r.committer,
58                              r.message,
59                              entries)
60
61    def _getUpstreamChangesets(self, sincerev):
62        """
63        See what other revisions exist upstream and return them
64        """
65        from bzrlib import fetch
66
67        parent = Branch.open(self.repository.repository)
68        repo = self._getRepo()
69
70        revisions = repo.missing_revisions(parent)
71        fetch.greedy_fetch(repo, parent)
72
73        for ri in revisions:
74            yield self._changesetFromRevision(parent, ri)
75
76    def _applyChangeset(self, changeset):
77        """
78        Apply given remote revision to workingdir
79        """
80        from bzrlib.merge import merge
81
82        repo = self._getRepo()
83        parent = Branch.open(self.repository.repository)
84
85        oldrevno = repo.revno()
86        self.log.info('Applying "%s" to current r%s', changeset.revision,
87                      oldrevno)
88        repo.append_revision(changeset.revision)
89        merge((self.basedir, -1), (self.basedir, oldrevno),
90              check_clean=False, this_dir=self.basedir)
91        self.log.debug("%s updated to %s",
92                       ', '.join([e.name for e in changeset.entries]),
93                       changeset.revision)
94        return [] # No conflicts for now
95
96    def _checkoutUpstreamRevision(self, revision):
97        """
98        Initial checkout, equivalent of 'bzr branch -r ... '
99        """
100        from bzrlib.clone import copy_branch
101
102        parent = Branch.open(self.repository.repository)
103
104        if revision == "INITIAL":
105            revid = parent.get_rev_id(1)
106        elif revision == "HEAD":
107            revid = None
108        else:
109            revid = revision
110
111        self.log.info('Extracting r%s out of "%s" in "%s"...',
112                      revid, parent, self.basedir)
113        self._b = copy_branch(parent, self.basedir, revid)
114
115        return self._changesetFromRevision(parent, revid)
116
117    ## SyncronizableTargetWorkingDir
118
119    def _addPathnames(self, entries):
120        # This method may get invoked several times with the same
121        # entries; and Branch.add complains if a file is already
122        # versioned.  So go through the list and sort out entries that
123        # is already versioned, since there is no need to add them.  A
124        # file can also already have been marked to be added in this
125        # changeset, or may be a target of a rename operation. Remove
126        # those files too. Do not try to catch any errors from
127        # Branch.add, since the they are _real_ errors.
128        last_revision = self._b.last_revision()
129        if last_revision is None:
130            # initial revision
131            new_entries = entries
132        else:
133            new_entries = []
134            inv = self._b.get_inventory(self._b.last_revision())
135            diff = compare_trees(self._b.revision_tree(last_revision),
136                                 self._b.working_tree())
137            added = ([new[0] for new in diff.added] +
138                     [renamed[1] for renamed in diff.renamed])
139
140            def parent_was_copied(n):
141                for p in added:
142                    if n.startswith(p+'/'):
143                        return True
144                return False
145
146            for e in entries:
147                if (not inv.has_filename(e)
148                    and not e in added
149                    and not parent_was_copied(e)):
150                    new_entries.append(e)
151                else:
152                    self.log.debug('"%s" already in inventory, skipping', e)
153
154        if len(new_entries) == 0:
155            return
156
157        self.log.info('Adding %s...', ', '.join(new_entries))
158        self._b.working_tree().add(new_entries)
159
160    def _commit(self, date, author, patchname, changelog=None, entries=None):
161        from time import mktime
162        from binascii import hexlify
163        from re import search
164        from bzrlib.osutils import compact_date, rand_bytes
165
166        logmessage = []
167        if patchname:
168            logmessage.append(patchname)
169        if changelog:
170            logmessage.append(changelog)
171        if logmessage:
172            self.log.info('Committing "%s"...', logmessage[0])
173            logmessage = '\n'.join(logmessage)
174        else:
175            self.log.info('Committing...')
176            logmessage = "Empty changelog"
177        timestamp = int(mktime(date.timetuple()))
178
179        # Guess sane email address
180        email = search("<(.*@.*)>", author)
181        if email:
182            email = email.group(1)
183        else:
184            email = author
185
186        revision_id = "%s-%s-%s" % (email, compact_date(timestamp),
187                                    hexlify(rand_bytes(8)))
188        self._b.working_tree().commit(logmessage, committer=author,
189                       specific_files=entries, rev_id=revision_id,
190                       verbose=self.repository.projectref().verbose,
191                       timestamp=timestamp)
192
193    def _removePathnames(self, entries):
194        """Remove a sequence of entries"""
195
196        self.log.info('Removing %s...', ', '.join(entries))
197        self._b.working_tree().remove(entries)
198
199    def _renamePathname(self, oldentry, newentry):
200        """Rename an entry"""
201
202        from os import rename
203        from os.path import join
204
205        # bzr does the rename itself as well
206        self.log.debug('Renaming "%s" back to "%s"', newentry, oldentry)
207        rename(join(self.basedir, newentry), join(self.basedir, oldentry))
208
209        self.log.info('Renaming "%s" to "%s"...', oldentry, newentry)
210        self._b.working_tree().rename_one(oldentry, newentry)
211
212    def _prepareTargetRepository(self):
213        """
214        Create the base directory if it doesn't exist, and the
215        repository as well in the new working directory.
216        """
217
218        from os.path import join, exists, split
219        from bzrlib import IGNORE_FILENAME
220
221        if not exists(join(self.basedir, self.repository.METADIR)):
222            ignored = []
223
224            # Omit our own log...
225            logfile = self.repository.projectref().logfile
226            dir, file = split(logfile)
227            if dir == self.basedir:
228                ignored.append(file)
229
230            # ... and state file
231            sfname = self.repository.projectref().state_file.filename
232            dir, file = split(sfname)
233            if dir == self.basedir:
234                ignored.append(file)
235                ignored.append(file+'.old')
236                ignored.append(file+'.journal')
237
238            if ignored:
239                bzrignore = open(join(self.basedir, IGNORE_FILENAME), 'wU')
240                bzrignore.write('\n'.join(ignored))
241
242            self.log.info('Initializing new repository in "%s"...',
243                          self.basedir)
244            self._b = Branch.initialize(self.basedir)
245        else:
246            self._b = Branch.open(self.basedir)
247
248    def _getRepo(self):
249        try:
250            return self._b
251        except AttributeError:
252            self._b = Branch.open(self.basedir)
253            return self._b
Note: See TracBrowser for help on using the repository browser.