/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
1
# Copyright (C) 2005 Canonical Ltd
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
16
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
17
from copy import deepcopy
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
18
from cStringIO import StringIO
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
19
from unittest import TestSuite
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
20
import xml.sax.saxutils
21
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
22
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
23
import bzrlib.bzrdir as bzrdir
1534.4.28 by Robert Collins
first cut at merge from integration.
24
from bzrlib.decorators import needs_read_lock, needs_write_lock
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
25
import bzrlib.errors as errors
1534.4.28 by Robert Collins
first cut at merge from integration.
26
from bzrlib.errors import InvalidRevisionId
1185.66.3 by Aaron Bentley
Renamed ControlFiles to LockableFiles
27
from bzrlib.lockable_files import LockableFiles
1534.4.28 by Robert Collins
first cut at merge from integration.
28
from bzrlib.osutils import safe_unicode
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
29
from bzrlib.revision import NULL_REVISION
1185.65.14 by Robert Collins
Merge from aaron. Whee, we are synced. Yay. Begone the foul demons of merge conflicts.
30
from bzrlib.store import copy_all
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
31
from bzrlib.store.weave import WeaveStore
32
from bzrlib.store.text import TextStore
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
33
from bzrlib.symbol_versioning import *
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
34
from bzrlib.trace import mutter
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
35
from bzrlib.tree import RevisionTree
36
from bzrlib.testament import Testament
1534.4.28 by Robert Collins
first cut at merge from integration.
37
from bzrlib.tree import EmptyTree
38
import bzrlib.xml5
1185.70.3 by Martin Pool
Various updates to make storage branch mergeable:
39
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
40
1185.66.5 by Aaron Bentley
Renamed RevisionStorage to Repository
41
class Repository(object):
1185.70.3 by Martin Pool
Various updates to make storage branch mergeable:
42
    """Repository holding history for one or more branches.
43
44
    The repository holds and retrieves historical information including
45
    revisions and file history.  It's normally accessed only by the Branch,
46
    which views a particular line of development through that history.
47
48
    The Repository builds on top of Stores and a Transport, which respectively 
49
    describe the disk data format and the way of accessing the (possibly 
50
    remote) disk.
51
    """
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
52
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
53
    @needs_read_lock
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
54
    def _all_possible_ids(self):
55
        """Return all the possible revisions that we could find."""
56
        return self.get_inventory_weave().names()
57
58
    @needs_read_lock
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
59
    def all_revision_ids(self):
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
60
        """Returns a list of all the revision ids in the repository. 
61
62
        These are in as much topological order as the underlying store can 
63
        present: for weaves ghosts may lead to a lack of correctness until
64
        the reweave updates the parents list.
65
        """
66
        result = self._all_possible_ids()
67
        return self._eliminate_revisions_not_present(result)
68
69
    @needs_read_lock
70
    def _eliminate_revisions_not_present(self, revision_ids):
71
        """Check every revision id in revision_ids to see if we have it.
72
73
        Returns a set of the present revisions.
74
        """
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
75
        result = []
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
76
        for id in revision_ids:
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
77
            if self.has_revision(id):
78
               result.append(id)
79
        return result
80
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
81
    @staticmethod
82
    def create(a_bzrdir):
83
        """Construct the current default format repository in a_bzrdir."""
84
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
85
86
    def __init__(self, transport, branch_format, _format=None, a_bzrdir=None):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
87
        object.__init__(self)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
88
        if transport is not None:
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
89
            warn("Repository.__init__(..., transport=XXX): The transport parameter is "
90
                 "deprecated and was never in a supported release. Please use "
91
                 "bzrdir.open_repository() or bzrdir.open_branch().repository.",
92
                 DeprecationWarning,
93
                 stacklevel=2)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
94
            self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
95
        else: 
96
            # TODO: clone into repository if needed
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
97
            self.control_files = LockableFiles(a_bzrdir.get_repository_transport(None), 'README')
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
98
99
        dir_mode = self.control_files._dir_mode
100
        file_mode = self.control_files._file_mode
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
101
        self._format = _format
102
        self.bzrdir = a_bzrdir
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
103
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
104
        def get_weave(name, prefixed=False):
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
105
            if name:
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
106
                name = safe_unicode(name)
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
107
            else:
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
108
                name = ''
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
109
            relpath = self.control_files._escape(name)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
110
            weave_transport = self.control_files._transport.clone(relpath)
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
111
            ws = WeaveStore(weave_transport, prefixed=prefixed,
112
                            dir_mode=dir_mode,
113
                            file_mode=file_mode)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
114
            if self.control_files._transport.should_cache():
115
                ws.enable_cache = True
116
            return ws
117
1534.4.28 by Robert Collins
first cut at merge from integration.
118
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
119
        def get_store(name, compressed=True, prefixed=False):
120
            # FIXME: This approach of assuming stores are all entirely compressed
121
            # or entirely uncompressed is tidy, but breaks upgrade from 
122
            # some existing branches where there's a mixture; we probably 
123
            # still want the option to look for both.
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
124
            if name:
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
125
                name = safe_unicode(name)
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
126
            else:
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
127
                name = ''
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
128
            relpath = self.control_files._escape(name)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
129
            store = TextStore(self.control_files._transport.clone(relpath),
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
130
                              prefixed=prefixed, compressed=compressed,
131
                              dir_mode=dir_mode,
132
                              file_mode=file_mode)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
133
            #if self._transport.should_cache():
134
            #    cache_path = os.path.join(self.cache_root, name)
135
            #    os.mkdir(cache_path)
136
            #    store = bzrlib.store.CachedStore(store, cache_path)
137
            return store
138
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
139
        if branch_format is not None:
140
            # circular dependencies:
141
            from bzrlib.branch import (BzrBranchFormat4,
142
                                       BzrBranchFormat5,
143
                                       BzrBranchFormat6,
144
                                       )
145
            if isinstance(branch_format, BzrBranchFormat4):
146
                self._format = RepositoryFormat4()
147
            elif isinstance(branch_format, BzrBranchFormat5):
148
                self._format = RepositoryFormat5()
149
            elif isinstance(branch_format, BzrBranchFormat6):
150
                self._format = RepositoryFormat6()
151
            
1534.4.28 by Robert Collins
first cut at merge from integration.
152
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
153
        if isinstance(self._format, RepositoryFormat4):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
154
            self.inventory_store = get_store('inventory-store')
155
            self.text_store = get_store('text-store')
156
            self.revision_store = get_store('revision-store')
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
157
        elif isinstance(self._format, RepositoryFormat5):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
158
            self.control_weaves = get_weave('')
159
            self.weave_store = get_weave('weaves')
160
            self.revision_store = get_store('revision-store', compressed=False)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
161
        elif isinstance(self._format, RepositoryFormat6):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
162
            self.control_weaves = get_weave('')
163
            self.weave_store = get_weave('weaves', prefixed=True)
164
            self.revision_store = get_store('revision-store', compressed=False,
165
                                            prefixed=True)
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
166
        elif isinstance(self._format, RepositoryFormat7):
167
            self.control_weaves = get_weave('')
168
            self.weave_store = get_weave('weaves', prefixed=True)
169
            self.revision_store = get_store('revision-store', compressed=False,
170
                                            prefixed=True)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
171
        self.revision_store.register_suffix('sig')
172
173
    def lock_write(self):
174
        self.control_files.lock_write()
175
176
    def lock_read(self):
177
        self.control_files.lock_read()
178
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
179
    @needs_read_lock
180
    def missing_revision_ids(self, other, revision_id=None):
181
        """Return the revision ids that other has that this does not.
182
        
183
        These are returned in topological order.
184
185
        revision_id: only return revision ids included by revision_id.
186
        """
187
        if self._compatible_formats(other):
188
            # fast path for weave-inventory based stores.
189
            # we want all revisions to satisft revision_id in other.
190
            # but we dont want to stat every file here and there.
191
            # we want then, all revisions other needs to satisfy revision_id 
192
            # checked, but not those that we have locally.
193
            # so the first thing is to get a subset of the revisions to 
194
            # satisfy revision_id in other, and then eliminate those that
195
            # we do already have. 
196
            # this is slow on high latency connection to self, but as as this
197
            # disk format scales terribly for push anyway due to rewriting 
198
            # inventory.weave, this is considered acceptable.
199
            # - RBC 20060209
200
            if revision_id is not None:
201
                other_ids = other.get_ancestry(revision_id)
202
                assert other_ids.pop(0) == None
203
            else:
204
                other_ids = other._all_possible_ids()
205
            other_ids_set = set(other_ids)
206
            # other ids is the worst case to pull now.
207
            # now we want to filter other_ids against what we actually
208
            # have, but dont try to stat what we know we dont.
209
            my_ids = set(self._all_possible_ids())
210
            possibly_present_revisions = my_ids.intersection(other_ids_set)
211
            actually_present_revisions = set(self._eliminate_revisions_not_present(possibly_present_revisions))
212
            required_revisions = other_ids_set.difference(actually_present_revisions)
213
            required_topo_revisions = [rev_id for rev_id in other_ids if rev_id in required_revisions]
214
            if revision_id is not None:
215
                # we used get_ancestry to determine other_ids then we are assured all
216
                # revisions referenced are present as they are installed in topological order.
217
                return required_topo_revisions
218
            else:
219
                # we only have an estimate of whats available
220
                return other._eliminate_revisions_not_present(required_topo_revisions)
221
        # slow code path.
222
        my_ids = set(self.all_revision_ids())
223
        if revision_id is not None:
224
            other_ids = other.get_ancestry(revision_id)
225
            assert other_ids.pop(0) == None
226
        else:
227
            other_ids = other.all_revision_ids()
228
        result_set = set(other_ids).difference(my_ids)
229
        return [rev_id for rev_id in other_ids if rev_id in result_set]
230
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
231
    @staticmethod
232
    def open(base):
233
        """Open the repository rooted at base.
234
235
        For instance, if the repository is at URL/.bzr/repository,
236
        Repository.open(URL) -> a Repository instance.
237
        """
238
        control = bzrdir.BzrDir.open(base)
239
        return control.open_repository()
240
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
241
    def _compatible_formats(self, other):
242
        """Return True if the stores in self and other are 'compatible'
243
        
244
        'compatible' means that they are both the same underlying type
245
        i.e. both weave stores, or both knits and thus support fast-path
246
        operations."""
247
        return (isinstance(self._format, (RepositoryFormat5,
248
                                          RepositoryFormat6,
249
                                          RepositoryFormat7)) and
250
                isinstance(other._format, (RepositoryFormat5,
251
                                           RepositoryFormat6,
252
                                           RepositoryFormat7)))
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
253
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
254
    @needs_read_lock
255
    def copy_content_into(self, destination, revision_id=None, basis=None):
1534.6.6 by Robert Collins
Move find_repository to bzrdir, its not quite ideal there but its simpler and until someone chooses to vary the search by branch type its completely sufficient.
256
        """Make a complete copy of the content in self into destination.
257
        
258
        This is a destructive operation! Do not use it on existing 
259
        repositories.
260
        """
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
261
        destination.lock_write()
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
262
        try:
1534.6.5 by Robert Collins
Cloning of repos preserves shared and make-working-tree attributes.
263
            try:
264
                destination.set_make_working_trees(self.make_working_trees())
265
            except NotImplementedError:
266
                pass
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
267
            # optimised paths:
268
            # compatible stores
269
            if self._compatible_formats(destination):
270
                if basis is not None:
271
                    # copy the basis in, then fetch remaining data.
272
                    basis.copy_content_into(destination, revision_id)
273
                    destination.fetch(self, revision_id=revision_id)
274
                else:
275
                    # FIXME do not peek!
276
                    if self.control_files._transport.listable():
277
                        destination.control_weaves.copy_multi(self.control_weaves,
278
                                                              ['inventory'])
279
                        copy_all(self.weave_store, destination.weave_store)
280
                        copy_all(self.revision_store, destination.revision_store)
281
                    else:
282
                        destination.fetch(self, revision_id=revision_id)
283
            # compatible v4 stores
284
            elif isinstance(self._format, RepositoryFormat4):
285
                if not isinstance(destination._format, RepositoryFormat4):
286
                    raise BzrError('cannot copy v4 branches to anything other than v4 branches.')
287
                store_pairs = ((self.text_store,      destination.text_store),
288
                               (self.inventory_store, destination.inventory_store),
289
                               (self.revision_store,  destination.revision_store))
290
                try:
291
                    for from_store, to_store in store_pairs: 
292
                        copy_all(from_store, to_store)
293
                except UnlistableStore:
294
                    raise UnlistableBranch(from_store)
295
            # fallback - 'fetch'
296
            else:
297
                destination.fetch(self, revision_id=revision_id)
298
        finally:
299
            destination.unlock()
300
301
    @needs_write_lock
302
    def fetch(self, source, revision_id=None):
303
        """Fetch the content required to construct revision_id from source.
304
305
        If revision_id is None all content is copied.
306
        """
307
        from bzrlib.fetch import RepoFetcher
308
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
309
               source, source._format, self, self._format)
310
        RepoFetcher(to_repository=self, from_repository=source, last_revision=revision_id)
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
311
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
312
    def unlock(self):
313
        self.control_files.unlock()
314
1185.65.27 by Robert Collins
Tweak storage towards mergability.
315
    @needs_read_lock
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
316
    def clone(self, a_bzrdir, revision_id=None, basis=None):
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
317
        """Clone this repository into a_bzrdir using the current format.
318
319
        Currently no check is made that the format of this repository and
320
        the bzrdir format are compatible. FIXME RBC 20060201.
321
        """
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
322
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
323
            # use target default format.
324
            result = a_bzrdir.create_repository()
325
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
326
        elif isinstance(a_bzrdir._format,
327
                      (bzrdir.BzrDirFormat4,
328
                       bzrdir.BzrDirFormat5,
329
                       bzrdir.BzrDirFormat6)):
330
            result = a_bzrdir.open_repository()
331
        else:
1534.6.5 by Robert Collins
Cloning of repos preserves shared and make-working-tree attributes.
332
            result = self._format.initialize(a_bzrdir, shared=self.is_shared())
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
333
        self.copy_content_into(result, revision_id, basis)
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
334
        return result
335
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
336
    def has_revision(self, revision_id):
337
        """True if this branch has a copy of the revision.
338
339
        This does not necessarily imply the revision is merge
340
        or on the mainline."""
341
        return (revision_id is None
342
                or self.revision_store.has_id(revision_id))
343
344
    @needs_read_lock
345
    def get_revision_xml_file(self, revision_id):
346
        """Return XML file object for revision object."""
347
        if not revision_id or not isinstance(revision_id, basestring):
348
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
349
        try:
350
            return self.revision_store.get(revision_id)
351
        except (IndexError, KeyError):
352
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
353
1185.65.27 by Robert Collins
Tweak storage towards mergability.
354
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
355
    def get_revision_xml(self, revision_id):
356
        return self.get_revision_xml_file(revision_id).read()
357
1185.65.27 by Robert Collins
Tweak storage towards mergability.
358
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
359
    def get_revision(self, revision_id):
360
        """Return the Revision object for a named revision"""
361
        xml_file = self.get_revision_xml_file(revision_id)
362
363
        try:
364
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
365
        except SyntaxError, e:
366
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
367
                                         [revision_id,
368
                                          str(e)])
369
            
370
        assert r.revision_id == revision_id
371
        return r
372
1185.65.27 by Robert Collins
Tweak storage towards mergability.
373
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
374
    def get_revision_sha1(self, revision_id):
375
        """Hash the stored value of a revision, and return it."""
376
        # In the future, revision entries will be signed. At that
377
        # point, it is probably best *not* to include the signature
378
        # in the revision hash. Because that lets you re-sign
379
        # the revision, (add signatures/remove signatures) and still
380
        # have all hash pointers stay consistent.
381
        # But for now, just hash the contents.
382
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
383
384
    @needs_write_lock
385
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
386
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
387
                                revision_id, "sig")
388
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
389
    def fileid_involved_between_revs(self, from_revid, to_revid):
390
        """Find file_id(s) which are involved in the changes between revisions.
391
392
        This determines the set of revisions which are involved, and then
393
        finds all file ids affected by those revisions.
394
        """
395
        # TODO: jam 20060119 This code assumes that w.inclusions will
396
        #       always be correct. But because of the presence of ghosts
397
        #       it is possible to be wrong.
398
        #       One specific example from Robert Collins:
399
        #       Two branches, with revisions ABC, and AD
400
        #       C is a ghost merge of D.
401
        #       Inclusions doesn't recognize D as an ancestor.
402
        #       If D is ever merged in the future, the weave
403
        #       won't be fixed, because AD never saw revision C
404
        #       to cause a conflict which would force a reweave.
405
        w = self.get_inventory_weave()
406
        from_set = set(w.inclusions([w.lookup(from_revid)]))
407
        to_set = set(w.inclusions([w.lookup(to_revid)]))
408
        included = to_set.difference(from_set)
409
        changed = map(w.idx_to_name, included)
410
        return self._fileid_involved_by_set(changed)
411
412
    def fileid_involved(self, last_revid=None):
413
        """Find all file_ids modified in the ancestry of last_revid.
414
415
        :param last_revid: If None, last_revision() will be used.
416
        """
417
        w = self.get_inventory_weave()
418
        if not last_revid:
419
            changed = set(w._names)
420
        else:
421
            included = w.inclusions([w.lookup(last_revid)])
422
            changed = map(w.idx_to_name, included)
423
        return self._fileid_involved_by_set(changed)
424
425
    def fileid_involved_by_set(self, changes):
426
        """Find all file_ids modified by the set of revisions passed in.
427
428
        :param changes: A set() of revision ids
429
        """
430
        # TODO: jam 20060119 This line does *nothing*, remove it.
431
        #       or better yet, change _fileid_involved_by_set so
432
        #       that it takes the inventory weave, rather than
433
        #       pulling it out by itself.
434
        return self._fileid_involved_by_set(changes)
435
436
    def _fileid_involved_by_set(self, changes):
437
        """Find the set of file-ids affected by the set of revisions.
438
439
        :param changes: A set() of revision ids.
440
        :return: A set() of file ids.
441
        
442
        This peaks at the Weave, interpreting each line, looking to
443
        see if it mentions one of the revisions. And if so, includes
444
        the file id mentioned.
445
        This expects both the Weave format, and the serialization
446
        to have a single line per file/directory, and to have
447
        fileid="" and revision="" on that line.
448
        """
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
449
        assert isinstance(self._format, (RepositoryFormat5,
450
                                         RepositoryFormat6,
451
                                         RepositoryFormat7)), \
452
            "fileid_involved only supported for branches which store inventory as unnested xml"
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
453
454
        w = self.get_inventory_weave()
455
        file_ids = set()
456
        for line in w._weave:
457
458
            # it is ugly, but it is due to the weave structure
459
            if not isinstance(line, basestring): continue
460
461
            start = line.find('file_id="')+9
462
            if start < 9: continue
463
            end = line.find('"', start)
464
            assert end>= 0
465
            file_id = xml.sax.saxutils.unescape(line[start:end])
466
467
            # check if file_id is already present
468
            if file_id in file_ids: continue
469
470
            start = line.find('revision="')+10
471
            if start < 10: continue
472
            end = line.find('"', start)
473
            assert end>= 0
474
            revision_id = xml.sax.saxutils.unescape(line[start:end])
475
476
            if revision_id in changes:
477
                file_ids.add(file_id)
478
        return file_ids
479
1185.65.27 by Robert Collins
Tweak storage towards mergability.
480
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
481
    def get_inventory_weave(self):
482
        return self.control_weaves.get_weave('inventory',
483
            self.get_transaction())
484
1185.65.27 by Robert Collins
Tweak storage towards mergability.
485
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
486
    def get_inventory(self, revision_id):
487
        """Get Inventory object by hash."""
488
        xml = self.get_inventory_xml(revision_id)
489
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
490
1185.65.27 by Robert Collins
Tweak storage towards mergability.
491
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
492
    def get_inventory_xml(self, revision_id):
493
        """Get inventory XML as a file object."""
494
        try:
495
            assert isinstance(revision_id, basestring), type(revision_id)
496
            iw = self.get_inventory_weave()
497
            return iw.get_text(iw.lookup(revision_id))
498
        except IndexError:
499
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
500
1185.65.27 by Robert Collins
Tweak storage towards mergability.
501
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
502
    def get_inventory_sha1(self, revision_id):
503
        """Return the sha1 hash of the inventory entry
504
        """
505
        return self.get_revision(revision_id).inventory_sha1
506
1185.65.27 by Robert Collins
Tweak storage towards mergability.
507
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
508
    def get_revision_inventory(self, revision_id):
509
        """Return inventory of a past revision."""
510
        # TODO: Unify this with get_inventory()
511
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
512
        # must be the same as its revision, so this is trivial.
1534.4.28 by Robert Collins
first cut at merge from integration.
513
        if revision_id is None:
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
514
            # This does not make sense: if there is no revision,
515
            # then it is the current tree inventory surely ?!
516
            # and thus get_root_id() is something that looks at the last
517
            # commit on the branch, and the get_root_id is an inventory check.
518
            raise NotImplementedError
519
            # return Inventory(self.get_root_id())
520
        else:
521
            return self.get_inventory(revision_id)
522
1185.65.27 by Robert Collins
Tweak storage towards mergability.
523
    @needs_read_lock
1534.6.3 by Robert Collins
find_repository sufficiently robust.
524
    def is_shared(self):
525
        """Return True if this repository is flagged as a shared repository."""
526
        # FIXME format 4-6 cannot be shared, this is technically faulty.
527
        return self.control_files._transport.has('shared-storage')
528
529
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
530
    def revision_tree(self, revision_id):
531
        """Return Tree for a revision on this branch.
532
533
        `revision_id` may be None for the null revision, in which case
534
        an `EmptyTree` is returned."""
535
        # TODO: refactor this to use an existing revision object
536
        # so we don't need to read it in twice.
1534.4.28 by Robert Collins
first cut at merge from integration.
537
        if revision_id is None or revision_id == NULL_REVISION:
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
538
            return EmptyTree()
539
        else:
540
            inv = self.get_revision_inventory(revision_id)
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
541
            return RevisionTree(self, inv, revision_id)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
542
1185.65.27 by Robert Collins
Tweak storage towards mergability.
543
    @needs_read_lock
1185.66.2 by Aaron Bentley
Moved get_ancestry to RevisionStorage
544
    def get_ancestry(self, revision_id):
545
        """Return a list of revision-ids integrated by a revision.
546
        
547
        This is topologically sorted.
548
        """
549
        if revision_id is None:
550
            return [None]
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
551
        if not self.has_revision(revision_id):
552
            raise errors.NoSuchRevision(self, revision_id)
1185.66.2 by Aaron Bentley
Moved get_ancestry to RevisionStorage
553
        w = self.get_inventory_weave()
554
        return [None] + map(w.idx_to_name,
555
                            w.inclusions([w.lookup(revision_id)]))
556
1185.65.4 by Aaron Bentley
Fixed cat command
557
    @needs_read_lock
558
    def print_file(self, file, revision_id):
1185.65.29 by Robert Collins
Implement final review suggestions.
559
        """Print `file` to stdout.
560
        
561
        FIXME RBC 20060125 as John Meinel points out this is a bad api
562
        - it writes to stdout, it assumes that that is valid etc. Fix
563
        by creating a new more flexible convenience function.
564
        """
1185.65.4 by Aaron Bentley
Fixed cat command
565
        tree = self.revision_tree(revision_id)
566
        # use inventory as it was in that revision
567
        file_id = tree.inventory.path2id(file)
568
        if not file_id:
569
            raise BzrError("%r is not present in revision %s" % (file, revno))
1185.65.15 by Robert Collins
Merge from integration.
570
            try:
571
                revno = self.revision_id_to_revno(revision_id)
572
            except errors.NoSuchRevision:
573
                # TODO: This should not be BzrError,
574
                # but NoSuchFile doesn't fit either
575
                raise BzrError('%r is not present in revision %s' 
576
                                % (file, revision_id))
577
            else:
578
                raise BzrError('%r is not present in revision %s'
579
                                % (file, revno))
1185.65.4 by Aaron Bentley
Fixed cat command
580
        tree.print_file(file_id)
581
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
582
    def get_transaction(self):
583
        return self.control_files.get_transaction()
584
1185.65.27 by Robert Collins
Tweak storage towards mergability.
585
    @needs_write_lock
1534.6.5 by Robert Collins
Cloning of repos preserves shared and make-working-tree attributes.
586
    def set_make_working_trees(self, new_value):
587
        """Set the policy flag for making working trees when creating branches.
588
589
        This only applies to branches that use this repository.
590
591
        The default is 'True'.
592
        :param new_value: True to restore the default, False to disable making
593
                          working trees.
594
        """
595
        # FIXME: split out into a new class/strategy ?
596
        if isinstance(self._format, (RepositoryFormat4,
597
                                     RepositoryFormat5,
598
                                     RepositoryFormat6)):
599
            raise NotImplementedError(self.set_make_working_trees)
600
        if new_value:
601
            try:
602
                self.control_files._transport.delete('no-working-trees')
603
            except errors.NoSuchFile:
1534.6.6 by Robert Collins
Move find_repository to bzrdir, its not quite ideal there but its simpler and until someone chooses to vary the search by branch type its completely sufficient.
604
                pass
1534.6.5 by Robert Collins
Cloning of repos preserves shared and make-working-tree attributes.
605
        else:
606
            self.control_files.put_utf8('no-working-trees', '')
607
    
608
    def make_working_trees(self):
609
        """Returns the policy for making working trees on new branches."""
610
        # FIXME: split out into a new class/strategy ?
611
        if isinstance(self._format, (RepositoryFormat4,
612
                                     RepositoryFormat5,
613
                                     RepositoryFormat6)):
614
            return True
1534.6.6 by Robert Collins
Move find_repository to bzrdir, its not quite ideal there but its simpler and until someone chooses to vary the search by branch type its completely sufficient.
615
        return not self.control_files._transport.has('no-working-trees')
1534.6.5 by Robert Collins
Cloning of repos preserves shared and make-working-tree attributes.
616
617
    @needs_write_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
618
    def sign_revision(self, revision_id, gpg_strategy):
619
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
620
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
621
622
623
class RepositoryFormat(object):
624
    """A repository format.
625
626
    Formats provide three things:
627
     * An initialization routine to construct repository data on disk.
628
     * a format string which is used when the BzrDir supports versioned
629
       children.
630
     * an open routine which returns a Repository instance.
631
632
    Formats are placed in an dict by their format string for reference 
633
    during opening. These should be subclasses of RepositoryFormat
634
    for consistency.
635
636
    Once a format is deprecated, just deprecate the initialize and open
637
    methods on the format class. Do not deprecate the object, as the 
638
    object will be created every system load.
639
640
    Common instance attributes:
641
    _matchingbzrdir - the bzrdir format that the repository format was
642
    originally written to work with. This can be used if manually
643
    constructing a bzrdir and repository, or more commonly for test suite
644
    parameterisation.
645
    """
646
647
    _default_format = None
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
648
    """The default format used for new repositories."""
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
649
650
    _formats = {}
651
    """The known formats."""
652
653
    @classmethod
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
654
    def find_format(klass, a_bzrdir):
655
        """Return the format for the repository object in a_bzrdir."""
656
        try:
657
            transport = a_bzrdir.get_repository_transport(None)
658
            format_string = transport.get("format").read()
659
            return klass._formats[format_string]
660
        except errors.NoSuchFile:
661
            raise errors.NoRepositoryPresent(a_bzrdir)
662
        except KeyError:
663
            raise errors.UnknownFormatError(format_string)
664
665
    @classmethod
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
666
    def get_default_format(klass):
667
        """Return the current default format."""
668
        return klass._default_format
669
670
    def get_format_string(self):
671
        """Return the ASCII format string that identifies this format.
672
        
673
        Note that in pre format ?? repositories the format string is 
674
        not permitted nor written to disk.
675
        """
676
        raise NotImplementedError(self.get_format_string)
677
1534.6.1 by Robert Collins
allow API creation of shared repositories
678
    def initialize(self, a_bzrdir, shared=False):
679
        """Initialize a repository of this format in a_bzrdir.
680
681
        :param a_bzrdir: The bzrdir to put the new repository in it.
682
        :param shared: The repository should be initialized as a sharable one.
683
684
        This may raise UninitializableFormat if shared repository are not
685
        compatible the a_bzrdir.
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
686
        """
687
688
    def is_supported(self):
689
        """Is this format supported?
690
691
        Supported formats must be initializable and openable.
692
        Unsupported formats may not support initialization or committing or 
693
        some other features depending on the reason for not being supported.
694
        """
695
        return True
696
697
    def open(self, a_bzrdir, _found=False):
698
        """Return an instance of this format for the bzrdir a_bzrdir.
699
        
700
        _found is a private parameter, do not use it.
701
        """
702
        if not _found:
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
703
            # we are being called directly and must probe.
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
704
            raise NotImplementedError
705
        return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
706
707
    @classmethod
708
    def register_format(klass, format):
709
        klass._formats[format.get_format_string()] = format
710
711
    @classmethod
712
    def set_default_format(klass, format):
713
        klass._default_format = format
714
715
    @classmethod
716
    def unregister_format(klass, format):
717
        assert klass._formats[format.get_format_string()] is format
718
        del klass._formats[format.get_format_string()]
719
720
1534.6.1 by Robert Collins
allow API creation of shared repositories
721
class PreSplitOutRepositoryFormat(RepositoryFormat):
722
    """Base class for the pre split out repository formats."""
723
724
    def initialize(self, a_bzrdir, shared=False, _internal=False):
725
        """Create a weave repository.
726
        
727
        TODO: when creating split out bzr branch formats, move this to a common
728
        base for Format5, Format6. or something like that.
729
        """
730
        from bzrlib.weavefile import write_weave_v5
731
        from bzrlib.weave import Weave
732
733
        if shared:
734
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
735
736
        if not _internal:
737
            # always initialized when the bzrdir is.
738
            return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
739
        
740
        # Create an empty weave
741
        sio = StringIO()
742
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
743
        empty_weave = sio.getvalue()
744
745
        mutter('creating repository in %s.', a_bzrdir.transport.base)
746
        dirs = ['revision-store', 'weaves']
747
        lock_file = 'branch-lock'
748
        files = [('inventory.weave', StringIO(empty_weave)), 
749
                 ]
750
        
751
        # FIXME: RBC 20060125 dont peek under the covers
752
        # NB: no need to escape relative paths that are url safe.
753
        control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
754
        control_files.lock_write()
755
        control_files._transport.mkdir_multi(dirs,
756
                mode=control_files._dir_mode)
757
        try:
758
            for file, content in files:
759
                control_files.put(file, content)
760
        finally:
761
            control_files.unlock()
762
        return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
763
764
765
class RepositoryFormat4(PreSplitOutRepositoryFormat):
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
766
    """Bzr repository format 4.
767
768
    This repository format has:
769
     - flat stores
770
     - TextStores for texts, inventories,revisions.
771
772
    This format is deprecated: it indexes texts using a text id which is
773
    removed in format 5; initializationa and write support for this format
774
    has been removed.
775
    """
776
777
    def __init__(self):
778
        super(RepositoryFormat4, self).__init__()
779
        self._matchingbzrdir = bzrdir.BzrDirFormat4()
780
1534.6.1 by Robert Collins
allow API creation of shared repositories
781
    def initialize(self, url, shared=False, _internal=False):
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
782
        """Format 4 branches cannot be created."""
783
        raise errors.UninitializableFormat(self)
784
785
    def is_supported(self):
786
        """Format 4 is not supported.
787
788
        It is not supported because the model changed from 4 to 5 and the
789
        conversion logic is expensive - so doing it on the fly was not 
790
        feasible.
791
        """
792
        return False
793
794
1534.6.1 by Robert Collins
allow API creation of shared repositories
795
class RepositoryFormat5(PreSplitOutRepositoryFormat):
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
796
    """Bzr control format 5.
797
798
    This repository format has:
799
     - weaves for file texts and inventory
800
     - flat stores
801
     - TextStores for revisions and signatures.
802
    """
803
804
    def __init__(self):
805
        super(RepositoryFormat5, self).__init__()
806
        self._matchingbzrdir = bzrdir.BzrDirFormat5()
807
808
1534.6.1 by Robert Collins
allow API creation of shared repositories
809
class RepositoryFormat6(PreSplitOutRepositoryFormat):
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
810
    """Bzr control format 6.
811
812
    This repository format has:
813
     - weaves for file texts and inventory
814
     - hash subdirectory based stores.
815
     - TextStores for revisions and signatures.
816
    """
817
818
    def __init__(self):
819
        super(RepositoryFormat6, self).__init__()
820
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
821
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
822
823
class RepositoryFormat7(RepositoryFormat):
824
    """Bzr repository 7.
825
826
    This repository format has:
827
     - weaves for file texts and inventory
828
     - hash subdirectory based stores.
829
     - TextStores for revisions and signatures.
830
     - a format marker of its own
1534.6.1 by Robert Collins
allow API creation of shared repositories
831
     - an optional 'shared-storage' flag
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
832
    """
833
834
    def get_format_string(self):
835
        """See RepositoryFormat.get_format_string()."""
836
        return "Bazaar-NG Repository format 7"
837
1534.6.1 by Robert Collins
allow API creation of shared repositories
838
    def initialize(self, a_bzrdir, shared=False):
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
839
        """Create a weave repository.
1534.6.1 by Robert Collins
allow API creation of shared repositories
840
841
        :param shared: If true the repository will be initialized as a shared
842
                       repository.
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
843
        """
844
        from bzrlib.weavefile import write_weave_v5
845
        from bzrlib.weave import Weave
846
847
        # Create an empty weave
848
        sio = StringIO()
849
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
850
        empty_weave = sio.getvalue()
851
852
        mutter('creating repository in %s.', a_bzrdir.transport.base)
853
        dirs = ['revision-store', 'weaves']
854
        files = [('inventory.weave', StringIO(empty_weave)), 
855
                 ]
856
        utf8_files = [('format', self.get_format_string())]
857
        
858
        # FIXME: RBC 20060125 dont peek under the covers
859
        # NB: no need to escape relative paths that are url safe.
860
        lock_file = 'lock'
861
        repository_transport = a_bzrdir.get_repository_transport(self)
862
        repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
863
        control_files = LockableFiles(repository_transport, 'lock')
864
        control_files.lock_write()
865
        control_files._transport.mkdir_multi(dirs,
866
                mode=control_files._dir_mode)
867
        try:
868
            for file, content in files:
869
                control_files.put(file, content)
870
            for file, content in utf8_files:
871
                control_files.put_utf8(file, content)
1534.6.1 by Robert Collins
allow API creation of shared repositories
872
            if shared == True:
873
                control_files.put_utf8('shared-storage', '')
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
874
        finally:
875
            control_files.unlock()
876
        return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
877
878
    def __init__(self):
879
        super(RepositoryFormat7, self).__init__()
880
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
881
882
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
883
# formats which have no format string are not discoverable
884
# and not independently creatable, so are not registered.
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
885
__default_format = RepositoryFormat7()
886
RepositoryFormat.register_format(__default_format)
887
RepositoryFormat.set_default_format(__default_format)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
888
_legacy_formats = [RepositoryFormat4(),
889
                   RepositoryFormat5(),
890
                   RepositoryFormat6()]
891
892
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
893
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
894
#       make sure that ancestry.weave is deleted (it is never used, but
895
#       used to be created)
896
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
897
class RepositoryTestProviderAdapter(object):
898
    """A tool to generate a suite testing multiple repository formats at once.
899
900
    This is done by copying the test once for each transport and injecting
901
    the transport_server, transport_readonly_server, and bzrdir_format and
902
    repository_format classes into each copy. Each copy is also given a new id()
903
    to make it easy to identify.
904
    """
905
906
    def __init__(self, transport_server, transport_readonly_server, formats):
907
        self._transport_server = transport_server
908
        self._transport_readonly_server = transport_readonly_server
909
        self._formats = formats
910
    
911
    def adapt(self, test):
912
        result = TestSuite()
913
        for repository_format, bzrdir_format in self._formats:
914
            new_test = deepcopy(test)
915
            new_test.transport_server = self._transport_server
916
            new_test.transport_readonly_server = self._transport_readonly_server
917
            new_test.bzrdir_format = bzrdir_format
918
            new_test.repository_format = repository_format
919
            def make_new_test_id():
920
                new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
921
                return lambda: new_id
922
            new_test.id = make_new_test_id()
923
            result.addTest(new_test)
924
        return result