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