~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

Merge integration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
from copy import deepcopy
17
18
from cStringIO import StringIO
18
 
 
 
19
from unittest import TestSuite
 
20
import xml.sax.saxutils
 
21
 
 
22
 
 
23
import bzrlib.bzrdir as bzrdir
19
24
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
25
import bzrlib.errors as errors
20
26
from bzrlib.errors import InvalidRevisionId
21
27
from bzrlib.lockable_files import LockableFiles
22
28
from bzrlib.osutils import safe_unicode
24
30
from bzrlib.store import copy_all
25
31
from bzrlib.store.weave import WeaveStore
26
32
from bzrlib.store.text import TextStore
 
33
from bzrlib.symbol_versioning import *
 
34
from bzrlib.trace import mutter
27
35
from bzrlib.tree import RevisionTree
28
36
from bzrlib.testament import Testament
29
37
from bzrlib.tree import EmptyTree
30
38
import bzrlib.xml5
31
39
 
32
40
 
33
 
 
34
41
class Repository(object):
35
42
    """Repository holding history for one or more branches.
36
43
 
43
50
    remote) disk.
44
51
    """
45
52
 
46
 
    def __init__(self, transport, branch_format):
47
 
        # circular dependencies:
48
 
        from bzrlib.branch import (BzrBranchFormat4,
49
 
                                   BzrBranchFormat5,
50
 
                                   BzrBranchFormat6,
51
 
                                   )
 
53
    @needs_read_lock
 
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
 
59
    def all_revision_ids(self):
 
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
        """
 
75
        result = []
 
76
        for id in revision_ids:
 
77
            if self.has_revision(id):
 
78
               result.append(id)
 
79
        return result
 
80
 
 
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):
52
87
        object.__init__(self)
53
 
        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
 
88
        if transport is not None:
 
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)
 
94
            self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
 
95
        else: 
 
96
            # TODO: clone into repository if needed
 
97
            self.control_files = LockableFiles(a_bzrdir.get_repository_transport(None), 'README')
54
98
 
55
99
        dir_mode = self.control_files._dir_mode
56
100
        file_mode = self.control_files._file_mode
 
101
        self._format = _format
 
102
        self.bzrdir = a_bzrdir
57
103
 
58
104
        def get_weave(name, prefixed=False):
59
105
            if name:
60
 
                name = bzrlib.BZRDIR + '/' + safe_unicode(name)
 
106
                name = safe_unicode(name)
61
107
            else:
62
 
                name = bzrlib.BZRDIR
 
108
                name = ''
63
109
            relpath = self.control_files._escape(name)
64
 
            weave_transport = transport.clone(relpath)
 
110
            weave_transport = self.control_files._transport.clone(relpath)
65
111
            ws = WeaveStore(weave_transport, prefixed=prefixed,
66
112
                            dir_mode=dir_mode,
67
113
                            file_mode=file_mode)
76
122
            # some existing branches where there's a mixture; we probably 
77
123
            # still want the option to look for both.
78
124
            if name:
79
 
                name = bzrlib.BZRDIR + '/' + safe_unicode(name)
 
125
                name = safe_unicode(name)
80
126
            else:
81
 
                name = bzrlib.BZRDIR
 
127
                name = ''
82
128
            relpath = self.control_files._escape(name)
83
 
            store = TextStore(transport.clone(relpath),
 
129
            store = TextStore(self.control_files._transport.clone(relpath),
84
130
                              prefixed=prefixed, compressed=compressed,
85
131
                              dir_mode=dir_mode,
86
132
                              file_mode=file_mode)
90
136
            #    store = bzrlib.store.CachedStore(store, cache_path)
91
137
            return store
92
138
 
 
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
            
93
152
 
94
 
        if isinstance(branch_format, BzrBranchFormat4):
 
153
        if isinstance(self._format, RepositoryFormat4):
95
154
            self.inventory_store = get_store('inventory-store')
96
155
            self.text_store = get_store('text-store')
97
156
            self.revision_store = get_store('revision-store')
98
 
        elif isinstance(branch_format, BzrBranchFormat5):
 
157
        elif isinstance(self._format, RepositoryFormat5):
99
158
            self.control_weaves = get_weave('')
100
159
            self.weave_store = get_weave('weaves')
101
160
            self.revision_store = get_store('revision-store', compressed=False)
102
 
        elif isinstance(branch_format, BzrBranchFormat6):
 
161
        elif isinstance(self._format, RepositoryFormat6):
 
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)
 
166
        elif isinstance(self._format, RepositoryFormat7):
103
167
            self.control_weaves = get_weave('')
104
168
            self.weave_store = get_weave('weaves', prefixed=True)
105
169
            self.revision_store = get_store('revision-store', compressed=False,
112
176
    def lock_read(self):
113
177
        self.control_files.lock_read()
114
178
 
 
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
 
 
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
 
 
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)))
 
253
 
 
254
    @needs_read_lock
 
255
    def copy_content_into(self, destination, revision_id=None, basis=None):
 
256
        """Make a complete copy of the content in self into destination."""
 
257
        destination.lock_write()
 
258
        try:
 
259
            # optimised paths:
 
260
            # compatible stores
 
261
            if self._compatible_formats(destination):
 
262
                if basis is not None:
 
263
                    # copy the basis in, then fetch remaining data.
 
264
                    basis.copy_content_into(destination, revision_id)
 
265
                    destination.fetch(self, revision_id=revision_id)
 
266
                else:
 
267
                    # FIXME do not peek!
 
268
                    if self.control_files._transport.listable():
 
269
                        destination.control_weaves.copy_multi(self.control_weaves,
 
270
                                                              ['inventory'])
 
271
                        copy_all(self.weave_store, destination.weave_store)
 
272
                        copy_all(self.revision_store, destination.revision_store)
 
273
                    else:
 
274
                        destination.fetch(self, revision_id=revision_id)
 
275
            # compatible v4 stores
 
276
            elif isinstance(self._format, RepositoryFormat4):
 
277
                if not isinstance(destination._format, RepositoryFormat4):
 
278
                    raise BzrError('cannot copy v4 branches to anything other than v4 branches.')
 
279
                store_pairs = ((self.text_store,      destination.text_store),
 
280
                               (self.inventory_store, destination.inventory_store),
 
281
                               (self.revision_store,  destination.revision_store))
 
282
                try:
 
283
                    for from_store, to_store in store_pairs: 
 
284
                        copy_all(from_store, to_store)
 
285
                except UnlistableStore:
 
286
                    raise UnlistableBranch(from_store)
 
287
            # fallback - 'fetch'
 
288
            else:
 
289
                destination.fetch(self, revision_id=revision_id)
 
290
        finally:
 
291
            destination.unlock()
 
292
 
 
293
    @needs_write_lock
 
294
    def fetch(self, source, revision_id=None):
 
295
        """Fetch the content required to construct revision_id from source.
 
296
 
 
297
        If revision_id is None all content is copied.
 
298
        """
 
299
        from bzrlib.fetch import RepoFetcher
 
300
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
301
               source, source._format, self, self._format)
 
302
        RepoFetcher(to_repository=self, from_repository=source, last_revision=revision_id)
 
303
 
115
304
    def unlock(self):
116
305
        self.control_files.unlock()
117
306
 
118
307
    @needs_read_lock
119
 
    def copy(self, destination):
120
 
        destination.lock_write()
121
 
        try:
122
 
            destination.control_weaves.copy_multi(self.control_weaves, 
123
 
                ['inventory'])
124
 
            copy_all(self.weave_store, destination.weave_store)
125
 
            copy_all(self.revision_store, destination.revision_store)
126
 
        finally:
127
 
            destination.unlock()
 
308
    def clone(self, a_bzrdir, revision_id=None, basis=None):
 
309
        """Clone this repository into a_bzrdir using the current format.
 
310
 
 
311
        Currently no check is made that the format of this repository and
 
312
        the bzrdir format are compatible. FIXME RBC 20060201.
 
313
        """
 
314
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
 
315
            # use target default format.
 
316
            result = a_bzrdir.create_repository()
 
317
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
 
318
        elif isinstance(a_bzrdir._format,
 
319
                      (bzrdir.BzrDirFormat4,
 
320
                       bzrdir.BzrDirFormat5,
 
321
                       bzrdir.BzrDirFormat6)):
 
322
            result = a_bzrdir.open_repository()
 
323
        else:
 
324
            result = self._format.initialize(a_bzrdir)
 
325
        self.copy_content_into(result, revision_id, basis)
 
326
        return result
128
327
 
129
328
    def has_revision(self, revision_id):
130
329
        """True if this branch has a copy of the revision.
179
378
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
180
379
                                revision_id, "sig")
181
380
 
 
381
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
382
        """Find file_id(s) which are involved in the changes between revisions.
 
383
 
 
384
        This determines the set of revisions which are involved, and then
 
385
        finds all file ids affected by those revisions.
 
386
        """
 
387
        # TODO: jam 20060119 This code assumes that w.inclusions will
 
388
        #       always be correct. But because of the presence of ghosts
 
389
        #       it is possible to be wrong.
 
390
        #       One specific example from Robert Collins:
 
391
        #       Two branches, with revisions ABC, and AD
 
392
        #       C is a ghost merge of D.
 
393
        #       Inclusions doesn't recognize D as an ancestor.
 
394
        #       If D is ever merged in the future, the weave
 
395
        #       won't be fixed, because AD never saw revision C
 
396
        #       to cause a conflict which would force a reweave.
 
397
        w = self.get_inventory_weave()
 
398
        from_set = set(w.inclusions([w.lookup(from_revid)]))
 
399
        to_set = set(w.inclusions([w.lookup(to_revid)]))
 
400
        included = to_set.difference(from_set)
 
401
        changed = map(w.idx_to_name, included)
 
402
        return self._fileid_involved_by_set(changed)
 
403
 
 
404
    def fileid_involved(self, last_revid=None):
 
405
        """Find all file_ids modified in the ancestry of last_revid.
 
406
 
 
407
        :param last_revid: If None, last_revision() will be used.
 
408
        """
 
409
        w = self.get_inventory_weave()
 
410
        if not last_revid:
 
411
            changed = set(w._names)
 
412
        else:
 
413
            included = w.inclusions([w.lookup(last_revid)])
 
414
            changed = map(w.idx_to_name, included)
 
415
        return self._fileid_involved_by_set(changed)
 
416
 
 
417
    def fileid_involved_by_set(self, changes):
 
418
        """Find all file_ids modified by the set of revisions passed in.
 
419
 
 
420
        :param changes: A set() of revision ids
 
421
        """
 
422
        # TODO: jam 20060119 This line does *nothing*, remove it.
 
423
        #       or better yet, change _fileid_involved_by_set so
 
424
        #       that it takes the inventory weave, rather than
 
425
        #       pulling it out by itself.
 
426
        return self._fileid_involved_by_set(changes)
 
427
 
 
428
    def _fileid_involved_by_set(self, changes):
 
429
        """Find the set of file-ids affected by the set of revisions.
 
430
 
 
431
        :param changes: A set() of revision ids.
 
432
        :return: A set() of file ids.
 
433
        
 
434
        This peaks at the Weave, interpreting each line, looking to
 
435
        see if it mentions one of the revisions. And if so, includes
 
436
        the file id mentioned.
 
437
        This expects both the Weave format, and the serialization
 
438
        to have a single line per file/directory, and to have
 
439
        fileid="" and revision="" on that line.
 
440
        """
 
441
        assert isinstance(self._format, (RepositoryFormat5,
 
442
                                         RepositoryFormat6,
 
443
                                         RepositoryFormat7)), \
 
444
            "fileid_involved only supported for branches which store inventory as unnested xml"
 
445
 
 
446
        w = self.get_inventory_weave()
 
447
        file_ids = set()
 
448
        for line in w._weave:
 
449
 
 
450
            # it is ugly, but it is due to the weave structure
 
451
            if not isinstance(line, basestring): continue
 
452
 
 
453
            start = line.find('file_id="')+9
 
454
            if start < 9: continue
 
455
            end = line.find('"', start)
 
456
            assert end>= 0
 
457
            file_id = xml.sax.saxutils.unescape(line[start:end])
 
458
 
 
459
            # check if file_id is already present
 
460
            if file_id in file_ids: continue
 
461
 
 
462
            start = line.find('revision="')+10
 
463
            if start < 10: continue
 
464
            end = line.find('"', start)
 
465
            assert end>= 0
 
466
            revision_id = xml.sax.saxutils.unescape(line[start:end])
 
467
 
 
468
            if revision_id in changes:
 
469
                file_ids.add(file_id)
 
470
        return file_ids
 
471
 
182
472
    @needs_read_lock
183
473
    def get_inventory_weave(self):
184
474
        return self.control_weaves.get_weave('inventory',
244
534
        """
245
535
        if revision_id is None:
246
536
            return [None]
 
537
        if not self.has_revision(revision_id):
 
538
            raise errors.NoSuchRevision(self, revision_id)
247
539
        w = self.get_inventory_weave()
248
540
        return [None] + map(w.idx_to_name,
249
541
                            w.inclusions([w.lookup(revision_id)]))
280
572
    def sign_revision(self, revision_id, gpg_strategy):
281
573
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
282
574
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
575
 
 
576
 
 
577
class RepositoryFormat(object):
 
578
    """A repository format.
 
579
 
 
580
    Formats provide three things:
 
581
     * An initialization routine to construct repository data on disk.
 
582
     * a format string which is used when the BzrDir supports versioned
 
583
       children.
 
584
     * an open routine which returns a Repository instance.
 
585
 
 
586
    Formats are placed in an dict by their format string for reference 
 
587
    during opening. These should be subclasses of RepositoryFormat
 
588
    for consistency.
 
589
 
 
590
    Once a format is deprecated, just deprecate the initialize and open
 
591
    methods on the format class. Do not deprecate the object, as the 
 
592
    object will be created every system load.
 
593
 
 
594
    Common instance attributes:
 
595
    _matchingbzrdir - the bzrdir format that the repository format was
 
596
    originally written to work with. This can be used if manually
 
597
    constructing a bzrdir and repository, or more commonly for test suite
 
598
    parameterisation.
 
599
    """
 
600
 
 
601
    _default_format = None
 
602
    """The default format used for new repositories."""
 
603
 
 
604
    _formats = {}
 
605
    """The known formats."""
 
606
 
 
607
    @classmethod
 
608
    def find_format(klass, a_bzrdir):
 
609
        """Return the format for the repository object in a_bzrdir."""
 
610
        try:
 
611
            transport = a_bzrdir.get_repository_transport(None)
 
612
            format_string = transport.get("format").read()
 
613
            return klass._formats[format_string]
 
614
        except errors.NoSuchFile:
 
615
            raise errors.NoRepositoryPresent(a_bzrdir)
 
616
        except KeyError:
 
617
            raise errors.UnknownFormatError(format_string)
 
618
 
 
619
    @classmethod
 
620
    def get_default_format(klass):
 
621
        """Return the current default format."""
 
622
        return klass._default_format
 
623
 
 
624
    def get_format_string(self):
 
625
        """Return the ASCII format string that identifies this format.
 
626
        
 
627
        Note that in pre format ?? repositories the format string is 
 
628
        not permitted nor written to disk.
 
629
        """
 
630
        raise NotImplementedError(self.get_format_string)
 
631
 
 
632
    def initialize(self, a_bzrdir, _internal=False):
 
633
        """Create a weave repository.
 
634
        
 
635
        TODO: when creating split out bzr branch formats, move this to a common
 
636
        base for Format5, Format6. or something like that.
 
637
        """
 
638
        from bzrlib.weavefile import write_weave_v5
 
639
        from bzrlib.weave import Weave
 
640
 
 
641
        if not _internal:
 
642
            # always initialized when the bzrdir is.
 
643
            return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
 
644
        
 
645
        # Create an empty weave
 
646
        sio = StringIO()
 
647
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
648
        empty_weave = sio.getvalue()
 
649
 
 
650
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
651
        dirs = ['revision-store', 'weaves']
 
652
        lock_file = 'branch-lock'
 
653
        files = [('inventory.weave', StringIO(empty_weave)), 
 
654
                 ]
 
655
        
 
656
        # FIXME: RBC 20060125 dont peek under the covers
 
657
        # NB: no need to escape relative paths that are url safe.
 
658
        control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
 
659
        control_files.lock_write()
 
660
        control_files._transport.mkdir_multi(dirs,
 
661
                mode=control_files._dir_mode)
 
662
        try:
 
663
            for file, content in files:
 
664
                control_files.put(file, content)
 
665
        finally:
 
666
            control_files.unlock()
 
667
        return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
 
668
 
 
669
    def is_supported(self):
 
670
        """Is this format supported?
 
671
 
 
672
        Supported formats must be initializable and openable.
 
673
        Unsupported formats may not support initialization or committing or 
 
674
        some other features depending on the reason for not being supported.
 
675
        """
 
676
        return True
 
677
 
 
678
    def open(self, a_bzrdir, _found=False):
 
679
        """Return an instance of this format for the bzrdir a_bzrdir.
 
680
        
 
681
        _found is a private parameter, do not use it.
 
682
        """
 
683
        if not _found:
 
684
            # we are being called directly and must probe.
 
685
            raise NotImplementedError
 
686
        return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
 
687
 
 
688
    @classmethod
 
689
    def register_format(klass, format):
 
690
        klass._formats[format.get_format_string()] = format
 
691
 
 
692
    @classmethod
 
693
    def set_default_format(klass, format):
 
694
        klass._default_format = format
 
695
 
 
696
    @classmethod
 
697
    def unregister_format(klass, format):
 
698
        assert klass._formats[format.get_format_string()] is format
 
699
        del klass._formats[format.get_format_string()]
 
700
 
 
701
 
 
702
class RepositoryFormat4(RepositoryFormat):
 
703
    """Bzr repository format 4.
 
704
 
 
705
    This repository format has:
 
706
     - flat stores
 
707
     - TextStores for texts, inventories,revisions.
 
708
 
 
709
    This format is deprecated: it indexes texts using a text id which is
 
710
    removed in format 5; initializationa and write support for this format
 
711
    has been removed.
 
712
    """
 
713
 
 
714
    def __init__(self):
 
715
        super(RepositoryFormat4, self).__init__()
 
716
        self._matchingbzrdir = bzrdir.BzrDirFormat4()
 
717
 
 
718
    def initialize(self, url, _internal=False):
 
719
        """Format 4 branches cannot be created."""
 
720
        raise errors.UninitializableFormat(self)
 
721
 
 
722
    def is_supported(self):
 
723
        """Format 4 is not supported.
 
724
 
 
725
        It is not supported because the model changed from 4 to 5 and the
 
726
        conversion logic is expensive - so doing it on the fly was not 
 
727
        feasible.
 
728
        """
 
729
        return False
 
730
 
 
731
 
 
732
class RepositoryFormat5(RepositoryFormat):
 
733
    """Bzr control format 5.
 
734
 
 
735
    This repository format has:
 
736
     - weaves for file texts and inventory
 
737
     - flat stores
 
738
     - TextStores for revisions and signatures.
 
739
    """
 
740
 
 
741
    def __init__(self):
 
742
        super(RepositoryFormat5, self).__init__()
 
743
        self._matchingbzrdir = bzrdir.BzrDirFormat5()
 
744
 
 
745
 
 
746
class RepositoryFormat6(RepositoryFormat):
 
747
    """Bzr control format 6.
 
748
 
 
749
    This repository format has:
 
750
     - weaves for file texts and inventory
 
751
     - hash subdirectory based stores.
 
752
     - TextStores for revisions and signatures.
 
753
    """
 
754
 
 
755
    def __init__(self):
 
756
        super(RepositoryFormat6, self).__init__()
 
757
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
758
 
 
759
 
 
760
class RepositoryFormat7(RepositoryFormat):
 
761
    """Bzr repository 7.
 
762
 
 
763
    This repository format has:
 
764
     - weaves for file texts and inventory
 
765
     - hash subdirectory based stores.
 
766
     - TextStores for revisions and signatures.
 
767
     - a format marker of its own
 
768
    """
 
769
 
 
770
    def get_format_string(self):
 
771
        """See RepositoryFormat.get_format_string()."""
 
772
        return "Bazaar-NG Repository format 7"
 
773
 
 
774
    def initialize(self, a_bzrdir):
 
775
        """Create a weave repository.
 
776
        """
 
777
        from bzrlib.weavefile import write_weave_v5
 
778
        from bzrlib.weave import Weave
 
779
 
 
780
        # Create an empty weave
 
781
        sio = StringIO()
 
782
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
783
        empty_weave = sio.getvalue()
 
784
 
 
785
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
786
        dirs = ['revision-store', 'weaves']
 
787
        files = [('inventory.weave', StringIO(empty_weave)), 
 
788
                 ]
 
789
        utf8_files = [('format', self.get_format_string())]
 
790
        
 
791
        # FIXME: RBC 20060125 dont peek under the covers
 
792
        # NB: no need to escape relative paths that are url safe.
 
793
        lock_file = 'lock'
 
794
        repository_transport = a_bzrdir.get_repository_transport(self)
 
795
        repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
 
796
        control_files = LockableFiles(repository_transport, 'lock')
 
797
        control_files.lock_write()
 
798
        control_files._transport.mkdir_multi(dirs,
 
799
                mode=control_files._dir_mode)
 
800
        try:
 
801
            for file, content in files:
 
802
                control_files.put(file, content)
 
803
            for file, content in utf8_files:
 
804
                control_files.put_utf8(file, content)
 
805
        finally:
 
806
            control_files.unlock()
 
807
        return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
 
808
 
 
809
    def __init__(self):
 
810
        super(RepositoryFormat7, self).__init__()
 
811
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
812
 
 
813
 
 
814
# formats which have no format string are not discoverable
 
815
# and not independently creatable, so are not registered.
 
816
__default_format = RepositoryFormat7()
 
817
RepositoryFormat.register_format(__default_format)
 
818
RepositoryFormat.set_default_format(__default_format)
 
819
_legacy_formats = [RepositoryFormat4(),
 
820
                   RepositoryFormat5(),
 
821
                   RepositoryFormat6()]
 
822
 
 
823
 
 
824
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
 
825
#       make sure that ancestry.weave is deleted (it is never used, but
 
826
#       used to be created)
 
827
 
 
828
class RepositoryTestProviderAdapter(object):
 
829
    """A tool to generate a suite testing multiple repository formats at once.
 
830
 
 
831
    This is done by copying the test once for each transport and injecting
 
832
    the transport_server, transport_readonly_server, and bzrdir_format and
 
833
    repository_format classes into each copy. Each copy is also given a new id()
 
834
    to make it easy to identify.
 
835
    """
 
836
 
 
837
    def __init__(self, transport_server, transport_readonly_server, formats):
 
838
        self._transport_server = transport_server
 
839
        self._transport_readonly_server = transport_readonly_server
 
840
        self._formats = formats
 
841
    
 
842
    def adapt(self, test):
 
843
        result = TestSuite()
 
844
        for repository_format, bzrdir_format in self._formats:
 
845
            new_test = deepcopy(test)
 
846
            new_test.transport_server = self._transport_server
 
847
            new_test.transport_readonly_server = self._transport_readonly_server
 
848
            new_test.bzrdir_format = bzrdir_format
 
849
            new_test.repository_format = repository_format
 
850
            def make_new_test_id():
 
851
                new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
 
852
                return lambda: new_id
 
853
            new_test.id = make_new_test_id()
 
854
            result.addTest(new_test)
 
855
        return result