~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Martin Pool
  • Date: 2006-03-09 03:28:52 UTC
  • mto: This revision was merged to the branch mainline in revision 1602.
  • Revision ID: mbp@sourcefrog.net-20060309032852-1097eb1947d9bceb
doc

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 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
 
16
 
 
17
from copy import deepcopy
 
18
from cStringIO import StringIO
 
19
from unittest import TestSuite
 
20
 
 
21
# FIXME: Pulling this in just for the unescape() routine seems like overkill.
 
22
import xml.sax.saxutils
 
23
 
 
24
import bzrlib.bzrdir as bzrdir
 
25
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
26
from bzrlib.errors import InvalidRevisionId
 
27
from bzrlib.lockable_files import LockableFiles, TransportLock
 
28
from bzrlib.lockdir import LockDir
 
29
from bzrlib.osutils import safe_unicode
 
30
from bzrlib.revision import NULL_REVISION
 
31
import bzrlib.errors as errors
 
32
import bzrlib.gpg as gpg
 
33
from bzrlib.store import copy_all
 
34
from bzrlib.store.weave import WeaveStore
 
35
from bzrlib.store.text import TextStore
 
36
from bzrlib.symbol_versioning import *
 
37
from bzrlib.trace import mutter
 
38
from bzrlib.tree import RevisionTree
 
39
from bzrlib.testament import Testament
 
40
from bzrlib.tree import EmptyTree
 
41
import bzrlib.ui
 
42
import bzrlib.xml5
 
43
 
 
44
 
 
45
class Repository(object):
 
46
    """Repository holding history for one or more branches.
 
47
 
 
48
    The repository holds and retrieves historical information including
 
49
    revisions and file history.  It's normally accessed only by the Branch,
 
50
    which views a particular line of development through that history.
 
51
 
 
52
    The Repository builds on top of Stores and a Transport, which respectively 
 
53
    describe the disk data format and the way of accessing the (possibly 
 
54
    remote) disk.
 
55
    """
 
56
 
 
57
    @needs_write_lock
 
58
    def add_inventory(self, revid, inv, parents):
 
59
        """Add the inventory inv to the repository as revid.
 
60
        
 
61
        :param parents: The revision ids of the parents that revid
 
62
                        is known to have and are in the repository already.
 
63
 
 
64
        returns the sha1 of the serialized inventory.
 
65
        """
 
66
        inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
67
        inv_sha1 = bzrlib.osutils.sha_string(inv_text)
 
68
        self.control_weaves.add_text('inventory', revid,
 
69
                   bzrlib.osutils.split_lines(inv_text), parents,
 
70
                   self.get_transaction())
 
71
        return inv_sha1
 
72
 
 
73
    @needs_write_lock
 
74
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
75
        """Add rev to the revision store as rev_id.
 
76
 
 
77
        :param rev_id: the revision id to use.
 
78
        :param rev: The revision object.
 
79
        :param inv: The inventory for the revision. if None, it will be looked
 
80
                    up in the inventory storer
 
81
        :param config: If None no digital signature will be created.
 
82
                       If supplied its signature_needed method will be used
 
83
                       to determine if a signature should be made.
 
84
        """
 
85
        if config is not None and config.signature_needed():
 
86
            if inv is None:
 
87
                inv = self.get_inventory(rev_id)
 
88
            plaintext = Testament(rev, inv).as_short_text()
 
89
            self.store_revision_signature(
 
90
                gpg.GPGStrategy(config), plaintext, rev_id)
 
91
        if not rev_id in self.get_inventory_weave():
 
92
            if inv is None:
 
93
                raise errors.WeaveRevisionNotPresent(rev_id,
 
94
                                                     self.get_inventory_weave())
 
95
            else:
 
96
                # yes, this is not suitable for adding with ghosts.
 
97
                self.add_inventory(rev_id, inv, rev.parent_ids)
 
98
            
 
99
        rev_tmp = StringIO()
 
100
        bzrlib.xml5.serializer_v5.write_revision(rev, rev_tmp)
 
101
        rev_tmp.seek(0)
 
102
        self.revision_store.add(rev_tmp, rev_id)
 
103
        mutter('added revision_id {%s}', rev_id)
 
104
 
 
105
    @needs_read_lock
 
106
    def _all_possible_ids(self):
 
107
        """Return all the possible revisions that we could find."""
 
108
        return self.get_inventory_weave().names()
 
109
 
 
110
    @needs_read_lock
 
111
    def all_revision_ids(self):
 
112
        """Returns a list of all the revision ids in the repository. 
 
113
 
 
114
        These are in as much topological order as the underlying store can 
 
115
        present: for weaves ghosts may lead to a lack of correctness until
 
116
        the reweave updates the parents list.
 
117
        """
 
118
        result = self._all_possible_ids()
 
119
        return self._eliminate_revisions_not_present(result)
 
120
 
 
121
    @needs_read_lock
 
122
    def _eliminate_revisions_not_present(self, revision_ids):
 
123
        """Check every revision id in revision_ids to see if we have it.
 
124
 
 
125
        Returns a set of the present revisions.
 
126
        """
 
127
        result = []
 
128
        for id in revision_ids:
 
129
            if self.has_revision(id):
 
130
               result.append(id)
 
131
        return result
 
132
 
 
133
    @staticmethod
 
134
    def create(a_bzrdir):
 
135
        """Construct the current default format repository in a_bzrdir."""
 
136
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
 
137
 
 
138
    def __init__(self, _format, a_bzrdir, control_files, revision_store):
 
139
        """instantiate a Repository.
 
140
 
 
141
        :param _format: The format of the repository on disk.
 
142
        :param a_bzrdir: The BzrDir of the repository.
 
143
 
 
144
        In the future we will have a single api for all stores for
 
145
        getting file texts, inventories and revisions, then
 
146
        this construct will accept instances of those things.
 
147
        """
 
148
        object.__init__(self)
 
149
        self._format = _format
 
150
        # the following are part of the public API for Repository:
 
151
        self.bzrdir = a_bzrdir
 
152
        self.control_files = control_files
 
153
        self.revision_store = revision_store
 
154
 
 
155
    def lock_write(self):
 
156
        self.control_files.lock_write()
 
157
 
 
158
    def lock_read(self):
 
159
        self.control_files.lock_read()
 
160
 
 
161
    def is_locked(self):
 
162
        return self.control_files.is_locked()
 
163
 
 
164
    @needs_read_lock
 
165
    def missing_revision_ids(self, other, revision_id=None):
 
166
        """Return the revision ids that other has that this does not.
 
167
        
 
168
        These are returned in topological order.
 
169
 
 
170
        revision_id: only return revision ids included by revision_id.
 
171
        """
 
172
        return InterRepository.get(other, self).missing_revision_ids(revision_id)
 
173
 
 
174
    @staticmethod
 
175
    def open(base):
 
176
        """Open the repository rooted at base.
 
177
 
 
178
        For instance, if the repository is at URL/.bzr/repository,
 
179
        Repository.open(URL) -> a Repository instance.
 
180
        """
 
181
        control = bzrlib.bzrdir.BzrDir.open(base)
 
182
        return control.open_repository()
 
183
 
 
184
    def copy_content_into(self, destination, revision_id=None, basis=None):
 
185
        """Make a complete copy of the content in self into destination.
 
186
        
 
187
        This is a destructive operation! Do not use it on existing 
 
188
        repositories.
 
189
        """
 
190
        return InterRepository.get(self, destination).copy_content(revision_id, basis)
 
191
 
 
192
    def fetch(self, source, revision_id=None, pb=None):
 
193
        """Fetch the content required to construct revision_id from source.
 
194
 
 
195
        If revision_id is None all content is copied.
 
196
        """
 
197
        return InterRepository.get(source, self).fetch(revision_id=revision_id,
 
198
                                                       pb=pb)
 
199
 
 
200
    def unlock(self):
 
201
        self.control_files.unlock()
 
202
 
 
203
    @needs_read_lock
 
204
    def clone(self, a_bzrdir, revision_id=None, basis=None):
 
205
        """Clone this repository into a_bzrdir using the current format.
 
206
 
 
207
        Currently no check is made that the format of this repository and
 
208
        the bzrdir format are compatible. FIXME RBC 20060201.
 
209
        """
 
210
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
 
211
            # use target default format.
 
212
            result = a_bzrdir.create_repository()
 
213
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
 
214
        elif isinstance(a_bzrdir._format,
 
215
                      (bzrlib.bzrdir.BzrDirFormat4,
 
216
                       bzrlib.bzrdir.BzrDirFormat5,
 
217
                       bzrlib.bzrdir.BzrDirFormat6)):
 
218
            result = a_bzrdir.open_repository()
 
219
        else:
 
220
            result = self._format.initialize(a_bzrdir, shared=self.is_shared())
 
221
        self.copy_content_into(result, revision_id, basis)
 
222
        return result
 
223
 
 
224
    def has_revision(self, revision_id):
 
225
        """True if this branch has a copy of the revision.
 
226
 
 
227
        This does not necessarily imply the revision is merge
 
228
        or on the mainline."""
 
229
        return (revision_id is None
 
230
                or self.revision_store.has_id(revision_id))
 
231
 
 
232
    @needs_read_lock
 
233
    def get_revision_xml_file(self, revision_id):
 
234
        """Return XML file object for revision object."""
 
235
        if not revision_id or not isinstance(revision_id, basestring):
 
236
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
 
237
        try:
 
238
            return self.revision_store.get(revision_id)
 
239
        except (IndexError, KeyError):
 
240
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
241
 
 
242
    @needs_read_lock
 
243
    def get_revision_xml(self, revision_id):
 
244
        return self.get_revision_xml_file(revision_id).read()
 
245
 
 
246
    @needs_read_lock
 
247
    def get_revision_reconcile(self, revision_id):
 
248
        """'reconcile' helper routine that allows access to a revision always.
 
249
        
 
250
        This variant of get_revision does not cross check the weave graph
 
251
        against the revision one as get_revision does: but it should only
 
252
        be used by reconcile, or reconcile-alike commands that are correcting
 
253
        or testing the revision graph.
 
254
        """
 
255
        xml_file = self.get_revision_xml_file(revision_id)
 
256
 
 
257
        try:
 
258
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
259
        except SyntaxError, e:
 
260
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
261
                                         [revision_id,
 
262
                                          str(e)])
 
263
            
 
264
        assert r.revision_id == revision_id
 
265
        return r
 
266
 
 
267
    @needs_read_lock
 
268
    def get_revision(self, revision_id):
 
269
        """Return the Revision object for a named revision"""
 
270
        r = self.get_revision_reconcile(revision_id)
 
271
        # weave corruption can lead to absent revision markers that should be
 
272
        # present.
 
273
        # the following test is reasonably cheap (it needs a single weave read)
 
274
        # and the weave is cached in read transactions. In write transactions
 
275
        # it is not cached but typically we only read a small number of
 
276
        # revisions. For knits when they are introduced we will probably want
 
277
        # to ensure that caching write transactions are in use.
 
278
        inv = self.get_inventory_weave()
 
279
        self._check_revision_parents(r, inv)
 
280
        return r
 
281
 
 
282
    def _check_revision_parents(self, revision, inventory):
 
283
        """Private to Repository and Fetch.
 
284
        
 
285
        This checks the parentage of revision in an inventory weave for 
 
286
        consistency and is only applicable to inventory-weave-for-ancestry
 
287
        using repository formats & fetchers.
 
288
        """
 
289
        weave_parents = inventory.parent_names(revision.revision_id)
 
290
        weave_names = inventory.names()
 
291
        for parent_id in revision.parent_ids:
 
292
            if parent_id in weave_names:
 
293
                # this parent must not be a ghost.
 
294
                if not parent_id in weave_parents:
 
295
                    # but it is a ghost
 
296
                    raise errors.CorruptRepository(self)
 
297
 
 
298
    @needs_read_lock
 
299
    def get_revision_sha1(self, revision_id):
 
300
        """Hash the stored value of a revision, and return it."""
 
301
        # In the future, revision entries will be signed. At that
 
302
        # point, it is probably best *not* to include the signature
 
303
        # in the revision hash. Because that lets you re-sign
 
304
        # the revision, (add signatures/remove signatures) and still
 
305
        # have all hash pointers stay consistent.
 
306
        # But for now, just hash the contents.
 
307
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
 
308
 
 
309
    @needs_write_lock
 
310
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
311
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
 
312
                                revision_id, "sig")
 
313
 
 
314
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
315
        """Find file_id(s) which are involved in the changes between revisions.
 
316
 
 
317
        This determines the set of revisions which are involved, and then
 
318
        finds all file ids affected by those revisions.
 
319
        """
 
320
        # TODO: jam 20060119 This code assumes that w.inclusions will
 
321
        #       always be correct. But because of the presence of ghosts
 
322
        #       it is possible to be wrong.
 
323
        #       One specific example from Robert Collins:
 
324
        #       Two branches, with revisions ABC, and AD
 
325
        #       C is a ghost merge of D.
 
326
        #       Inclusions doesn't recognize D as an ancestor.
 
327
        #       If D is ever merged in the future, the weave
 
328
        #       won't be fixed, because AD never saw revision C
 
329
        #       to cause a conflict which would force a reweave.
 
330
        w = self.get_inventory_weave()
 
331
        from_set = set(w.inclusions([w.lookup(from_revid)]))
 
332
        to_set = set(w.inclusions([w.lookup(to_revid)]))
 
333
        included = to_set.difference(from_set)
 
334
        changed = map(w.idx_to_name, included)
 
335
        return self._fileid_involved_by_set(changed)
 
336
 
 
337
    def fileid_involved(self, last_revid=None):
 
338
        """Find all file_ids modified in the ancestry of last_revid.
 
339
 
 
340
        :param last_revid: If None, last_revision() will be used.
 
341
        """
 
342
        w = self.get_inventory_weave()
 
343
        if not last_revid:
 
344
            changed = set(w._names)
 
345
        else:
 
346
            included = w.inclusions([w.lookup(last_revid)])
 
347
            changed = map(w.idx_to_name, included)
 
348
        return self._fileid_involved_by_set(changed)
 
349
 
 
350
    def fileid_involved_by_set(self, changes):
 
351
        """Find all file_ids modified by the set of revisions passed in.
 
352
 
 
353
        :param changes: A set() of revision ids
 
354
        """
 
355
        # TODO: jam 20060119 This line does *nothing*, remove it.
 
356
        #       or better yet, change _fileid_involved_by_set so
 
357
        #       that it takes the inventory weave, rather than
 
358
        #       pulling it out by itself.
 
359
        return self._fileid_involved_by_set(changes)
 
360
 
 
361
    def _fileid_involved_by_set(self, changes):
 
362
        """Find the set of file-ids affected by the set of revisions.
 
363
 
 
364
        :param changes: A set() of revision ids.
 
365
        :return: A set() of file ids.
 
366
        
 
367
        This peaks at the Weave, interpreting each line, looking to
 
368
        see if it mentions one of the revisions. And if so, includes
 
369
        the file id mentioned.
 
370
        This expects both the Weave format, and the serialization
 
371
        to have a single line per file/directory, and to have
 
372
        fileid="" and revision="" on that line.
 
373
        """
 
374
        assert isinstance(self._format, (RepositoryFormat5,
 
375
                                         RepositoryFormat6,
 
376
                                         RepositoryFormat7,
 
377
                                         RepositoryFormatKnit1)), \
 
378
            "fileid_involved only supported for branches which store inventory as unnested xml"
 
379
 
 
380
        w = self.get_inventory_weave()
 
381
        file_ids = set()
 
382
        for line in w._weave:
 
383
 
 
384
            # it is ugly, but it is due to the weave structure
 
385
            if not isinstance(line, basestring): continue
 
386
 
 
387
            start = line.find('file_id="')+9
 
388
            if start < 9: continue
 
389
            end = line.find('"', start)
 
390
            assert end>= 0
 
391
            file_id = xml.sax.saxutils.unescape(line[start:end])
 
392
 
 
393
            # check if file_id is already present
 
394
            if file_id in file_ids: continue
 
395
 
 
396
            start = line.find('revision="')+10
 
397
            if start < 10: continue
 
398
            end = line.find('"', start)
 
399
            assert end>= 0
 
400
            revision_id = xml.sax.saxutils.unescape(line[start:end])
 
401
 
 
402
            if revision_id in changes:
 
403
                file_ids.add(file_id)
 
404
        return file_ids
 
405
 
 
406
    @needs_read_lock
 
407
    def get_inventory_weave(self):
 
408
        return self.control_weaves.get_weave('inventory',
 
409
            self.get_transaction())
 
410
 
 
411
    @needs_read_lock
 
412
    def get_inventory(self, revision_id):
 
413
        """Get Inventory object by hash."""
 
414
        xml = self.get_inventory_xml(revision_id)
 
415
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
416
 
 
417
    @needs_read_lock
 
418
    def get_inventory_xml(self, revision_id):
 
419
        """Get inventory XML as a file object."""
 
420
        try:
 
421
            assert isinstance(revision_id, basestring), type(revision_id)
 
422
            iw = self.get_inventory_weave()
 
423
            return iw.get_text(iw.lookup(revision_id))
 
424
        except IndexError:
 
425
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
426
 
 
427
    @needs_read_lock
 
428
    def get_inventory_sha1(self, revision_id):
 
429
        """Return the sha1 hash of the inventory entry
 
430
        """
 
431
        return self.get_revision(revision_id).inventory_sha1
 
432
 
 
433
    @needs_read_lock
 
434
    def get_revision_inventory(self, revision_id):
 
435
        """Return inventory of a past revision."""
 
436
        # TODO: Unify this with get_inventory()
 
437
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
438
        # must be the same as its revision, so this is trivial.
 
439
        if revision_id is None:
 
440
            # This does not make sense: if there is no revision,
 
441
            # then it is the current tree inventory surely ?!
 
442
            # and thus get_root_id() is something that looks at the last
 
443
            # commit on the branch, and the get_root_id is an inventory check.
 
444
            raise NotImplementedError
 
445
            # return Inventory(self.get_root_id())
 
446
        else:
 
447
            return self.get_inventory(revision_id)
 
448
 
 
449
    @needs_read_lock
 
450
    def is_shared(self):
 
451
        """Return True if this repository is flagged as a shared repository."""
 
452
        # FIXME format 4-6 cannot be shared, this is technically faulty.
 
453
        return self.control_files._transport.has('shared-storage')
 
454
 
 
455
    @needs_read_lock
 
456
    def revision_tree(self, revision_id):
 
457
        """Return Tree for a revision on this branch.
 
458
 
 
459
        `revision_id` may be None for the null revision, in which case
 
460
        an `EmptyTree` is returned."""
 
461
        # TODO: refactor this to use an existing revision object
 
462
        # so we don't need to read it in twice.
 
463
        if revision_id is None or revision_id == NULL_REVISION:
 
464
            return EmptyTree()
 
465
        else:
 
466
            inv = self.get_revision_inventory(revision_id)
 
467
            return RevisionTree(self, inv, revision_id)
 
468
 
 
469
    @needs_read_lock
 
470
    def get_ancestry(self, revision_id):
 
471
        """Return a list of revision-ids integrated by a revision.
 
472
        
 
473
        This is topologically sorted.
 
474
        """
 
475
        if revision_id is None:
 
476
            return [None]
 
477
        if not self.has_revision(revision_id):
 
478
            raise errors.NoSuchRevision(self, revision_id)
 
479
        w = self.get_inventory_weave()
 
480
        return [None] + map(w.idx_to_name,
 
481
                            w.inclusions([w.lookup(revision_id)]))
 
482
 
 
483
    @needs_read_lock
 
484
    def print_file(self, file, revision_id):
 
485
        """Print `file` to stdout.
 
486
        
 
487
        FIXME RBC 20060125 as John Meinel points out this is a bad api
 
488
        - it writes to stdout, it assumes that that is valid etc. Fix
 
489
        by creating a new more flexible convenience function.
 
490
        """
 
491
        tree = self.revision_tree(revision_id)
 
492
        # use inventory as it was in that revision
 
493
        file_id = tree.inventory.path2id(file)
 
494
        if not file_id:
 
495
            raise BzrError("%r is not present in revision %s" % (file, revno))
 
496
            try:
 
497
                revno = self.revision_id_to_revno(revision_id)
 
498
            except errors.NoSuchRevision:
 
499
                # TODO: This should not be BzrError,
 
500
                # but NoSuchFile doesn't fit either
 
501
                raise BzrError('%r is not present in revision %s' 
 
502
                                % (file, revision_id))
 
503
            else:
 
504
                raise BzrError('%r is not present in revision %s'
 
505
                                % (file, revno))
 
506
        tree.print_file(file_id)
 
507
 
 
508
    def get_transaction(self):
 
509
        return self.control_files.get_transaction()
 
510
 
 
511
    @needs_write_lock
 
512
    def set_make_working_trees(self, new_value):
 
513
        """Set the policy flag for making working trees when creating branches.
 
514
 
 
515
        This only applies to branches that use this repository.
 
516
 
 
517
        The default is 'True'.
 
518
        :param new_value: True to restore the default, False to disable making
 
519
                          working trees.
 
520
        """
 
521
        # FIXME: split out into a new class/strategy ?
 
522
        if isinstance(self._format, (RepositoryFormat4,
 
523
                                     RepositoryFormat5,
 
524
                                     RepositoryFormat6)):
 
525
            raise NotImplementedError(self.set_make_working_trees)
 
526
        if new_value:
 
527
            try:
 
528
                self.control_files._transport.delete('no-working-trees')
 
529
            except errors.NoSuchFile:
 
530
                pass
 
531
        else:
 
532
            self.control_files.put_utf8('no-working-trees', '')
 
533
    
 
534
    def make_working_trees(self):
 
535
        """Returns the policy for making working trees on new branches."""
 
536
        # FIXME: split out into a new class/strategy ?
 
537
        if isinstance(self._format, (RepositoryFormat4,
 
538
                                     RepositoryFormat5,
 
539
                                     RepositoryFormat6)):
 
540
            return True
 
541
        return not self.control_files._transport.has('no-working-trees')
 
542
 
 
543
    @needs_write_lock
 
544
    def sign_revision(self, revision_id, gpg_strategy):
 
545
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
546
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
547
 
 
548
 
 
549
class AllInOneRepository(Repository):
 
550
    """Legacy support - the repository behaviour for all-in-one branches."""
 
551
 
 
552
    def __init__(self, _format, a_bzrdir, revision_store):
 
553
        # we reuse one control files instance.
 
554
        dir_mode = a_bzrdir._control_files._dir_mode
 
555
        file_mode = a_bzrdir._control_files._file_mode
 
556
 
 
557
        def get_weave(name, prefixed=False):
 
558
            if name:
 
559
                name = safe_unicode(name)
 
560
            else:
 
561
                name = ''
 
562
            relpath = a_bzrdir._control_files._escape(name)
 
563
            weave_transport = a_bzrdir._control_files._transport.clone(relpath)
 
564
            ws = WeaveStore(weave_transport, prefixed=prefixed,
 
565
                            dir_mode=dir_mode,
 
566
                            file_mode=file_mode)
 
567
            if a_bzrdir._control_files._transport.should_cache():
 
568
                ws.enable_cache = True
 
569
            return ws
 
570
 
 
571
        def get_store(name, compressed=True, prefixed=False):
 
572
            # FIXME: This approach of assuming stores are all entirely compressed
 
573
            # or entirely uncompressed is tidy, but breaks upgrade from 
 
574
            # some existing branches where there's a mixture; we probably 
 
575
            # still want the option to look for both.
 
576
            relpath = a_bzrdir._control_files._escape(name)
 
577
            store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
 
578
                              prefixed=prefixed, compressed=compressed,
 
579
                              dir_mode=dir_mode,
 
580
                              file_mode=file_mode)
 
581
            #if self._transport.should_cache():
 
582
            #    cache_path = os.path.join(self.cache_root, name)
 
583
            #    os.mkdir(cache_path)
 
584
            #    store = bzrlib.store.CachedStore(store, cache_path)
 
585
            return store
 
586
 
 
587
        # not broken out yet because the controlweaves|inventory_store
 
588
        # and text_store | weave_store bits are still different.
 
589
        if isinstance(_format, RepositoryFormat4):
 
590
            self.inventory_store = get_store('inventory-store')
 
591
            self.text_store = get_store('text-store')
 
592
        elif isinstance(_format, RepositoryFormat5):
 
593
            self.control_weaves = get_weave('')
 
594
            self.weave_store = get_weave('weaves')
 
595
        elif isinstance(_format, RepositoryFormat6):
 
596
            self.control_weaves = get_weave('')
 
597
            self.weave_store = get_weave('weaves', prefixed=True)
 
598
        else:
 
599
            raise errors.BzrError('unreachable code: unexpected repository'
 
600
                                  ' format.')
 
601
        revision_store.register_suffix('sig')
 
602
        super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, revision_store)
 
603
 
 
604
 
 
605
class MetaDirRepository(Repository):
 
606
    """Repositories in the new meta-dir layout."""
 
607
 
 
608
    def __init__(self, _format, a_bzrdir, control_files, revision_store):
 
609
        super(MetaDirRepository, self).__init__(_format,
 
610
                                                a_bzrdir,
 
611
                                                control_files,
 
612
                                                revision_store)
 
613
 
 
614
        dir_mode = self.control_files._dir_mode
 
615
        file_mode = self.control_files._file_mode
 
616
 
 
617
        def get_weave(name, prefixed=False):
 
618
            if name:
 
619
                name = safe_unicode(name)
 
620
            else:
 
621
                name = ''
 
622
            relpath = self.control_files._escape(name)
 
623
            weave_transport = self.control_files._transport.clone(relpath)
 
624
            ws = WeaveStore(weave_transport, prefixed=prefixed,
 
625
                            dir_mode=dir_mode,
 
626
                            file_mode=file_mode)
 
627
            if self.control_files._transport.should_cache():
 
628
                ws.enable_cache = True
 
629
            return ws
 
630
 
 
631
        if isinstance(self._format, RepositoryFormat7):
 
632
            self.control_weaves = get_weave('')
 
633
            self.weave_store = get_weave('weaves', prefixed=True)
 
634
        elif isinstance(self._format, RepositoryFormatKnit1):
 
635
            self.control_weaves = get_weave('')
 
636
            self.weave_store = get_weave('knits', prefixed=True)
 
637
        else:
 
638
            raise errors.BzrError('unreachable code: unexpected repository'
 
639
                                  ' format.')
 
640
 
 
641
 
 
642
class RepositoryFormat(object):
 
643
    """A repository format.
 
644
 
 
645
    Formats provide three things:
 
646
     * An initialization routine to construct repository data on disk.
 
647
     * a format string which is used when the BzrDir supports versioned
 
648
       children.
 
649
     * an open routine which returns a Repository instance.
 
650
 
 
651
    Formats are placed in an dict by their format string for reference 
 
652
    during opening. These should be subclasses of RepositoryFormat
 
653
    for consistency.
 
654
 
 
655
    Once a format is deprecated, just deprecate the initialize and open
 
656
    methods on the format class. Do not deprecate the object, as the 
 
657
    object will be created every system load.
 
658
 
 
659
    Common instance attributes:
 
660
    _matchingbzrdir - the bzrdir format that the repository format was
 
661
    originally written to work with. This can be used if manually
 
662
    constructing a bzrdir and repository, or more commonly for test suite
 
663
    parameterisation.
 
664
    """
 
665
 
 
666
    _default_format = None
 
667
    """The default format used for new repositories."""
 
668
 
 
669
    _formats = {}
 
670
    """The known formats."""
 
671
 
 
672
    @classmethod
 
673
    def find_format(klass, a_bzrdir):
 
674
        """Return the format for the repository object in a_bzrdir."""
 
675
        try:
 
676
            transport = a_bzrdir.get_repository_transport(None)
 
677
            format_string = transport.get("format").read()
 
678
            return klass._formats[format_string]
 
679
        except errors.NoSuchFile:
 
680
            raise errors.NoRepositoryPresent(a_bzrdir)
 
681
        except KeyError:
 
682
            raise errors.UnknownFormatError(format_string)
 
683
 
 
684
    @classmethod
 
685
    def get_default_format(klass):
 
686
        """Return the current default format."""
 
687
        return klass._default_format
 
688
 
 
689
    def get_format_string(self):
 
690
        """Return the ASCII format string that identifies this format.
 
691
        
 
692
        Note that in pre format ?? repositories the format string is 
 
693
        not permitted nor written to disk.
 
694
        """
 
695
        raise NotImplementedError(self.get_format_string)
 
696
 
 
697
    def _get_revision_store(self, repo_transport, control_files):
 
698
        """Return the revision store object for this a_bzrdir."""
 
699
        raise NotImplementedError(self._get_revision_store)
 
700
 
 
701
    def _get_rev_store(self,
 
702
                   transport,
 
703
                   control_files,
 
704
                   name,
 
705
                   compressed=True,
 
706
                   prefixed=False):
 
707
        """Common logic for getting a revision store for a repository.
 
708
        
 
709
        see self._get_revision_store for the method to 
 
710
        get the store for a repository.
 
711
        """
 
712
        if name:
 
713
            name = safe_unicode(name)
 
714
        else:
 
715
            name = ''
 
716
        dir_mode = control_files._dir_mode
 
717
        file_mode = control_files._file_mode
 
718
        revision_store =TextStore(transport.clone(name),
 
719
                                  prefixed=prefixed,
 
720
                                  compressed=compressed,
 
721
                                  dir_mode=dir_mode,
 
722
                                  file_mode=file_mode)
 
723
        revision_store.register_suffix('sig')
 
724
        return revision_store
 
725
 
 
726
    def initialize(self, a_bzrdir, shared=False):
 
727
        """Initialize a repository of this format in a_bzrdir.
 
728
 
 
729
        :param a_bzrdir: The bzrdir to put the new repository in it.
 
730
        :param shared: The repository should be initialized as a sharable one.
 
731
 
 
732
        This may raise UninitializableFormat if shared repository are not
 
733
        compatible the a_bzrdir.
 
734
        """
 
735
 
 
736
    def is_supported(self):
 
737
        """Is this format supported?
 
738
 
 
739
        Supported formats must be initializable and openable.
 
740
        Unsupported formats may not support initialization or committing or 
 
741
        some other features depending on the reason for not being supported.
 
742
        """
 
743
        return True
 
744
 
 
745
    def open(self, a_bzrdir, _found=False):
 
746
        """Return an instance of this format for the bzrdir a_bzrdir.
 
747
        
 
748
        _found is a private parameter, do not use it.
 
749
        """
 
750
        raise NotImplementedError(self.open)
 
751
 
 
752
    @classmethod
 
753
    def register_format(klass, format):
 
754
        klass._formats[format.get_format_string()] = format
 
755
 
 
756
    @classmethod
 
757
    def set_default_format(klass, format):
 
758
        klass._default_format = format
 
759
 
 
760
    @classmethod
 
761
    def unregister_format(klass, format):
 
762
        assert klass._formats[format.get_format_string()] is format
 
763
        del klass._formats[format.get_format_string()]
 
764
 
 
765
 
 
766
class PreSplitOutRepositoryFormat(RepositoryFormat):
 
767
    """Base class for the pre split out repository formats."""
 
768
 
 
769
    def initialize(self, a_bzrdir, shared=False, _internal=False):
 
770
        """Create a weave repository.
 
771
        
 
772
        TODO: when creating split out bzr branch formats, move this to a common
 
773
        base for Format5, Format6. or something like that.
 
774
        """
 
775
        from bzrlib.weavefile import write_weave_v5
 
776
        from bzrlib.weave import Weave
 
777
 
 
778
        if shared:
 
779
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
780
 
 
781
        if not _internal:
 
782
            # always initialized when the bzrdir is.
 
783
            return self.open(a_bzrdir, _found=True)
 
784
        
 
785
        # Create an empty weave
 
786
        sio = StringIO()
 
787
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
788
        empty_weave = sio.getvalue()
 
789
 
 
790
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
791
        dirs = ['revision-store', 'weaves']
 
792
        files = [('inventory.weave', StringIO(empty_weave)),
 
793
                 ]
 
794
        
 
795
        # FIXME: RBC 20060125 dont peek under the covers
 
796
        # NB: no need to escape relative paths that are url safe.
 
797
        control_files = LockableFiles(a_bzrdir.transport, 'branch-lock',
 
798
                                      TransportLock)
 
799
        control_files.create_lock()
 
800
        control_files.lock_write()
 
801
        control_files._transport.mkdir_multi(dirs,
 
802
                mode=control_files._dir_mode)
 
803
        try:
 
804
            for file, content in files:
 
805
                control_files.put(file, content)
 
806
        finally:
 
807
            control_files.unlock()
 
808
        return self.open(a_bzrdir, _found=True)
 
809
 
 
810
    def open(self, a_bzrdir, _found=False):
 
811
        """See RepositoryFormat.open()."""
 
812
        if not _found:
 
813
            # we are being called directly and must probe.
 
814
            raise NotImplementedError
 
815
 
 
816
        repo_transport = a_bzrdir.get_repository_transport(None)
 
817
        control_files = a_bzrdir._control_files
 
818
        revision_store = self._get_revision_store(repo_transport, control_files)
 
819
        return AllInOneRepository(_format=self,
 
820
                                  a_bzrdir=a_bzrdir,
 
821
                                  revision_store=revision_store)
 
822
 
 
823
 
 
824
class RepositoryFormat4(PreSplitOutRepositoryFormat):
 
825
    """Bzr repository format 4.
 
826
 
 
827
    This repository format has:
 
828
     - flat stores
 
829
     - TextStores for texts, inventories,revisions.
 
830
 
 
831
    This format is deprecated: it indexes texts using a text id which is
 
832
    removed in format 5; initializationa and write support for this format
 
833
    has been removed.
 
834
    """
 
835
 
 
836
    def __init__(self):
 
837
        super(RepositoryFormat4, self).__init__()
 
838
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat4()
 
839
 
 
840
    def initialize(self, url, shared=False, _internal=False):
 
841
        """Format 4 branches cannot be created."""
 
842
        raise errors.UninitializableFormat(self)
 
843
 
 
844
    def is_supported(self):
 
845
        """Format 4 is not supported.
 
846
 
 
847
        It is not supported because the model changed from 4 to 5 and the
 
848
        conversion logic is expensive - so doing it on the fly was not 
 
849
        feasible.
 
850
        """
 
851
        return False
 
852
 
 
853
    def _get_revision_store(self, repo_transport, control_files):
 
854
        """See RepositoryFormat._get_revision_store()."""
 
855
        return self._get_rev_store(repo_transport,
 
856
                                   control_files,
 
857
                                   'revision-store')
 
858
 
 
859
 
 
860
class RepositoryFormat5(PreSplitOutRepositoryFormat):
 
861
    """Bzr control format 5.
 
862
 
 
863
    This repository format has:
 
864
     - weaves for file texts and inventory
 
865
     - flat stores
 
866
     - TextStores for revisions and signatures.
 
867
    """
 
868
 
 
869
    def __init__(self):
 
870
        super(RepositoryFormat5, self).__init__()
 
871
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat5()
 
872
 
 
873
    def _get_revision_store(self, repo_transport, control_files):
 
874
        """See RepositoryFormat._get_revision_store()."""
 
875
        """Return the revision store object for this a_bzrdir."""
 
876
        return self._get_rev_store(repo_transport,
 
877
                                   control_files,
 
878
                                   'revision-store',
 
879
                                   compressed=False)
 
880
 
 
881
 
 
882
class RepositoryFormat6(PreSplitOutRepositoryFormat):
 
883
    """Bzr control format 6.
 
884
 
 
885
    This repository format has:
 
886
     - weaves for file texts and inventory
 
887
     - hash subdirectory based stores.
 
888
     - TextStores for revisions and signatures.
 
889
    """
 
890
 
 
891
    def __init__(self):
 
892
        super(RepositoryFormat6, self).__init__()
 
893
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirFormat6()
 
894
 
 
895
    def _get_revision_store(self, repo_transport, control_files):
 
896
        """See RepositoryFormat._get_revision_store()."""
 
897
        return self._get_rev_store(repo_transport,
 
898
                                   control_files,
 
899
                                   'revision-store',
 
900
                                   compressed=False,
 
901
                                   prefixed=True)
 
902
 
 
903
 
 
904
class MetaDirRepositoryFormat(RepositoryFormat):
 
905
    """Common base class for the new repositories using the metadir layour."""
 
906
 
 
907
    def __init__(self):
 
908
        super(MetaDirRepositoryFormat, self).__init__()
 
909
        self._matchingbzrdir = bzrlib.bzrdir.BzrDirMetaFormat1()
 
910
 
 
911
    def _create_control_files(self, a_bzrdir):
 
912
        """Create the required files and the initial control_files object."""
 
913
        # FIXME: RBC 20060125 dont peek under the covers
 
914
        # NB: no need to escape relative paths that are url safe.
 
915
        repository_transport = a_bzrdir.get_repository_transport(self)
 
916
        control_files = LockableFiles(repository_transport, 'lock', LockDir)
 
917
        control_files.create_lock()
 
918
        return control_files
 
919
 
 
920
    def _get_revision_store(self, repo_transport, control_files):
 
921
        """See RepositoryFormat._get_revision_store()."""
 
922
        return self._get_rev_store(repo_transport,
 
923
                                   control_files,
 
924
                                   'revision-store',
 
925
                                   compressed=False,
 
926
                                   prefixed=True,
 
927
                                   )
 
928
 
 
929
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
930
        """See RepositoryFormat.open().
 
931
        
 
932
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
933
                                    repository at a slightly different url
 
934
                                    than normal. I.e. during 'upgrade'.
 
935
        """
 
936
        if not _found:
 
937
            format = RepositoryFormat.find_format(a_bzrdir)
 
938
            assert format.__class__ ==  self.__class__
 
939
        if _override_transport is not None:
 
940
            repo_transport = _override_transport
 
941
        else:
 
942
            repo_transport = a_bzrdir.get_repository_transport(None)
 
943
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
 
944
        revision_store = self._get_revision_store(repo_transport, control_files)
 
945
        return MetaDirRepository(_format=self,
 
946
                                 a_bzrdir=a_bzrdir,
 
947
                                 control_files=control_files,
 
948
                                 revision_store=revision_store)
 
949
 
 
950
    def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
 
951
        """Upload the initial blank content."""
 
952
        control_files = self._create_control_files(a_bzrdir)
 
953
        control_files.lock_write()
 
954
        try:
 
955
            control_files._transport.mkdir_multi(dirs,
 
956
                    mode=control_files._dir_mode)
 
957
            for file, content in files:
 
958
                control_files.put(file, content)
 
959
            for file, content in utf8_files:
 
960
                control_files.put_utf8(file, content)
 
961
            if shared == True:
 
962
                control_files.put_utf8('shared-storage', '')
 
963
        finally:
 
964
            control_files.unlock()
 
965
 
 
966
 
 
967
class RepositoryFormat7(MetaDirRepositoryFormat):
 
968
    """Bzr repository 7.
 
969
 
 
970
    This repository format has:
 
971
     - weaves for file texts and inventory
 
972
     - hash subdirectory based stores.
 
973
     - TextStores for revisions and signatures.
 
974
     - a format marker of its own
 
975
     - an optional 'shared-storage' flag
 
976
     - an optional 'no-working-trees' flag
 
977
    """
 
978
 
 
979
    def get_format_string(self):
 
980
        """See RepositoryFormat.get_format_string()."""
 
981
        return "Bazaar-NG Repository format 7"
 
982
 
 
983
    def initialize(self, a_bzrdir, shared=False):
 
984
        """Create a weave repository.
 
985
 
 
986
        :param shared: If true the repository will be initialized as a shared
 
987
                       repository.
 
988
        """
 
989
        from bzrlib.weavefile import write_weave_v5
 
990
        from bzrlib.weave import Weave
 
991
 
 
992
        # Create an empty weave
 
993
        sio = StringIO()
 
994
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
995
        empty_weave = sio.getvalue()
 
996
 
 
997
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
998
        dirs = ['revision-store', 'weaves']
 
999
        files = [('inventory.weave', StringIO(empty_weave)), 
 
1000
                 ]
 
1001
        utf8_files = [('format', self.get_format_string())]
 
1002
 
 
1003
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1004
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
1005
 
 
1006
 
 
1007
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
 
1008
    """Bzr repository knit format 1.
 
1009
 
 
1010
    This repository format has:
 
1011
     - knits for file texts and inventory
 
1012
     - hash subdirectory based stores.
 
1013
     - knits for revisions and signatures
 
1014
     - TextStores for revisions and signatures.
 
1015
     - a format marker of its own
 
1016
     - an optional 'shared-storage' flag
 
1017
     - an optional 'no-working-trees' flag
 
1018
     - a LockDir lock
 
1019
    """
 
1020
 
 
1021
    def get_format_string(self):
 
1022
        """See RepositoryFormat.get_format_string()."""
 
1023
        return "Bazaar-NG Knit Repository Format 1"
 
1024
 
 
1025
    def initialize(self, a_bzrdir, shared=False):
 
1026
        """Create a knit format 1 repository.
 
1027
 
 
1028
        :param shared: If true the repository will be initialized as a shared
 
1029
                       repository.
 
1030
        XXX NOTE that this current uses a Weave for testing and will become 
 
1031
            A Knit in due course.
 
1032
        """
 
1033
        from bzrlib.weavefile import write_weave_v5
 
1034
        from bzrlib.weave import Weave
 
1035
 
 
1036
        # Create an empty weave
 
1037
        sio = StringIO()
 
1038
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
1039
        empty_weave = sio.getvalue()
 
1040
 
 
1041
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1042
        dirs = ['revision-store', 'knits']
 
1043
        files = [('inventory.weave', StringIO(empty_weave)), 
 
1044
                 ]
 
1045
        utf8_files = [('format', self.get_format_string())]
 
1046
        
 
1047
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1048
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
1049
 
 
1050
 
 
1051
# formats which have no format string are not discoverable
 
1052
# and not independently creatable, so are not registered.
 
1053
_default_format = RepositoryFormat7()
 
1054
RepositoryFormat.register_format(_default_format)
 
1055
RepositoryFormat.register_format(RepositoryFormatKnit1())
 
1056
RepositoryFormat.set_default_format(_default_format)
 
1057
_legacy_formats = [RepositoryFormat4(),
 
1058
                   RepositoryFormat5(),
 
1059
                   RepositoryFormat6()]
 
1060
 
 
1061
 
 
1062
class InterRepository(object):
 
1063
    """This class represents operations taking place between two repositories.
 
1064
 
 
1065
    Its instances have methods like copy_content and fetch, and contain
 
1066
    references to the source and target repositories these operations can be 
 
1067
    carried out on.
 
1068
 
 
1069
    Often we will provide convenience methods on 'repository' which carry out
 
1070
    operations with another repository - they will always forward to
 
1071
    InterRepository.get(other).method_name(parameters).
 
1072
    """
 
1073
    # XXX: FIXME: FUTURE: robertc
 
1074
    # testing of these probably requires a factory in optimiser type, and 
 
1075
    # then a test adapter to test each type thoroughly.
 
1076
    #
 
1077
 
 
1078
    _optimisers = set()
 
1079
    """The available optimised InterRepository types."""
 
1080
 
 
1081
    def __init__(self, source, target):
 
1082
        """Construct a default InterRepository instance. Please use 'get'.
 
1083
        
 
1084
        Only subclasses of InterRepository should call 
 
1085
        InterRepository.__init__ - clients should call InterRepository.get
 
1086
        instead which will create an optimised InterRepository if possible.
 
1087
        """
 
1088
        self.source = source
 
1089
        self.target = target
 
1090
 
 
1091
    @needs_write_lock
 
1092
    def copy_content(self, revision_id=None, basis=None):
 
1093
        """Make a complete copy of the content in self into destination.
 
1094
        
 
1095
        This is a destructive operation! Do not use it on existing 
 
1096
        repositories.
 
1097
 
 
1098
        :param revision_id: Only copy the content needed to construct
 
1099
                            revision_id and its parents.
 
1100
        :param basis: Copy the needed data preferentially from basis.
 
1101
        """
 
1102
        try:
 
1103
            self.target.set_make_working_trees(self.source.make_working_trees())
 
1104
        except NotImplementedError:
 
1105
            pass
 
1106
        # grab the basis available data
 
1107
        if basis is not None:
 
1108
            self.target.fetch(basis, revision_id=revision_id)
 
1109
        # but dont both fetching if we have the needed data now.
 
1110
        if (revision_id not in (None, NULL_REVISION) and 
 
1111
            self.target.has_revision(revision_id)):
 
1112
            return
 
1113
        self.target.fetch(self.source, revision_id=revision_id)
 
1114
 
 
1115
    def _double_lock(self, lock_source, lock_target):
 
1116
        """Take out too locks, rolling back the first if the second throws."""
 
1117
        lock_source()
 
1118
        try:
 
1119
            lock_target()
 
1120
        except Exception:
 
1121
            # we want to ensure that we don't leave source locked by mistake.
 
1122
            # and any error on target should not confuse source.
 
1123
            self.source.unlock()
 
1124
            raise
 
1125
 
 
1126
    @needs_write_lock
 
1127
    def fetch(self, revision_id=None, pb=None):
 
1128
        """Fetch the content required to construct revision_id.
 
1129
 
 
1130
        The content is copied from source to target.
 
1131
 
 
1132
        :param revision_id: if None all content is copied, if NULL_REVISION no
 
1133
                            content is copied.
 
1134
        :param pb: optional progress bar to use for progress reports. If not
 
1135
                   provided a default one will be created.
 
1136
 
 
1137
        Returns the copied revision count and the failed revisions in a tuple:
 
1138
        (copied, failures).
 
1139
        """
 
1140
        from bzrlib.fetch import RepoFetcher
 
1141
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
1142
               self.source, self.source._format, self.target, self.target._format)
 
1143
        f = RepoFetcher(to_repository=self.target,
 
1144
                        from_repository=self.source,
 
1145
                        last_revision=revision_id,
 
1146
                        pb=pb)
 
1147
        return f.count_copied, f.failed_revisions
 
1148
 
 
1149
    @classmethod
 
1150
    def get(klass, repository_source, repository_target):
 
1151
        """Retrieve a InterRepository worker object for these repositories.
 
1152
 
 
1153
        :param repository_source: the repository to be the 'source' member of
 
1154
                                  the InterRepository instance.
 
1155
        :param repository_target: the repository to be the 'target' member of
 
1156
                                the InterRepository instance.
 
1157
        If an optimised InterRepository worker exists it will be used otherwise
 
1158
        a default InterRepository instance will be created.
 
1159
        """
 
1160
        for provider in klass._optimisers:
 
1161
            if provider.is_compatible(repository_source, repository_target):
 
1162
                return provider(repository_source, repository_target)
 
1163
        return InterRepository(repository_source, repository_target)
 
1164
 
 
1165
    def lock_read(self):
 
1166
        """Take out a logical read lock.
 
1167
 
 
1168
        This will lock the source branch and the target branch. The source gets
 
1169
        a read lock and the target a read lock.
 
1170
        """
 
1171
        self._double_lock(self.source.lock_read, self.target.lock_read)
 
1172
 
 
1173
    def lock_write(self):
 
1174
        """Take out a logical write lock.
 
1175
 
 
1176
        This will lock the source branch and the target branch. The source gets
 
1177
        a read lock and the target a write lock.
 
1178
        """
 
1179
        self._double_lock(self.source.lock_read, self.target.lock_write)
 
1180
 
 
1181
    @needs_read_lock
 
1182
    def missing_revision_ids(self, revision_id=None):
 
1183
        """Return the revision ids that source has that target does not.
 
1184
        
 
1185
        These are returned in topological order.
 
1186
 
 
1187
        :param revision_id: only return revision ids included by this
 
1188
                            revision_id.
 
1189
        """
 
1190
        # generic, possibly worst case, slow code path.
 
1191
        target_ids = set(self.target.all_revision_ids())
 
1192
        if revision_id is not None:
 
1193
            source_ids = self.source.get_ancestry(revision_id)
 
1194
            assert source_ids.pop(0) == None
 
1195
        else:
 
1196
            source_ids = self.source.all_revision_ids()
 
1197
        result_set = set(source_ids).difference(target_ids)
 
1198
        # this may look like a no-op: its not. It preserves the ordering
 
1199
        # other_ids had while only returning the members from other_ids
 
1200
        # that we've decided we need.
 
1201
        return [rev_id for rev_id in source_ids if rev_id in result_set]
 
1202
 
 
1203
    @classmethod
 
1204
    def register_optimiser(klass, optimiser):
 
1205
        """Register an InterRepository optimiser."""
 
1206
        klass._optimisers.add(optimiser)
 
1207
 
 
1208
    def unlock(self):
 
1209
        """Release the locks on source and target."""
 
1210
        try:
 
1211
            self.target.unlock()
 
1212
        finally:
 
1213
            self.source.unlock()
 
1214
 
 
1215
    @classmethod
 
1216
    def unregister_optimiser(klass, optimiser):
 
1217
        """Unregister an InterRepository optimiser."""
 
1218
        klass._optimisers.remove(optimiser)
 
1219
 
 
1220
 
 
1221
class InterWeaveRepo(InterRepository):
 
1222
    """Optimised code paths between Weave based repositories."""
 
1223
 
 
1224
    _matching_repo_format = _default_format
 
1225
    """Repository format for testing with."""
 
1226
 
 
1227
    @staticmethod
 
1228
    def is_compatible(source, target):
 
1229
        """Be compatible with known Weave formats.
 
1230
        
 
1231
        We dont test for the stores being of specific types becase that
 
1232
        could lead to confusing results, and there is no need to be 
 
1233
        overly general.
 
1234
        """
 
1235
        try:
 
1236
            return (isinstance(source._format, (RepositoryFormat5,
 
1237
                                                RepositoryFormat6,
 
1238
                                                RepositoryFormat7)) and
 
1239
                    isinstance(target._format, (RepositoryFormat5,
 
1240
                                                RepositoryFormat6,
 
1241
                                                RepositoryFormat7)))
 
1242
        except AttributeError:
 
1243
            return False
 
1244
    
 
1245
    @needs_write_lock
 
1246
    def copy_content(self, revision_id=None, basis=None):
 
1247
        """See InterRepository.copy_content()."""
 
1248
        # weave specific optimised path:
 
1249
        if basis is not None:
 
1250
            # copy the basis in, then fetch remaining data.
 
1251
            basis.copy_content_into(self.target, revision_id)
 
1252
            # the basis copy_content_into could misset this.
 
1253
            try:
 
1254
                self.target.set_make_working_trees(self.source.make_working_trees())
 
1255
            except NotImplementedError:
 
1256
                pass
 
1257
            self.target.fetch(self.source, revision_id=revision_id)
 
1258
        else:
 
1259
            try:
 
1260
                self.target.set_make_working_trees(self.source.make_working_trees())
 
1261
            except NotImplementedError:
 
1262
                pass
 
1263
            # FIXME do not peek!
 
1264
            if self.source.control_files._transport.listable():
 
1265
                pb = bzrlib.ui.ui_factory.progress_bar()
 
1266
                copy_all(self.source.weave_store,
 
1267
                    self.target.weave_store, pb=pb)
 
1268
                pb.update('copying inventory', 0, 1)
 
1269
                self.target.control_weaves.copy_multi(
 
1270
                    self.source.control_weaves, ['inventory'])
 
1271
                copy_all(self.source.revision_store,
 
1272
                    self.target.revision_store, pb=pb)
 
1273
            else:
 
1274
                self.target.fetch(self.source, revision_id=revision_id)
 
1275
 
 
1276
    @needs_write_lock
 
1277
    def fetch(self, revision_id=None, pb=None):
 
1278
        """See InterRepository.fetch()."""
 
1279
        from bzrlib.fetch import RepoFetcher
 
1280
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
1281
               self.source, self.source._format, self.target, self.target._format)
 
1282
        f = RepoFetcher(to_repository=self.target,
 
1283
                        from_repository=self.source,
 
1284
                        last_revision=revision_id,
 
1285
                        pb=pb)
 
1286
        return f.count_copied, f.failed_revisions
 
1287
 
 
1288
    @needs_read_lock
 
1289
    def missing_revision_ids(self, revision_id=None):
 
1290
        """See InterRepository.missing_revision_ids()."""
 
1291
        # we want all revisions to satisfy revision_id in source.
 
1292
        # but we dont want to stat every file here and there.
 
1293
        # we want then, all revisions other needs to satisfy revision_id 
 
1294
        # checked, but not those that we have locally.
 
1295
        # so the first thing is to get a subset of the revisions to 
 
1296
        # satisfy revision_id in source, and then eliminate those that
 
1297
        # we do already have. 
 
1298
        # this is slow on high latency connection to self, but as as this
 
1299
        # disk format scales terribly for push anyway due to rewriting 
 
1300
        # inventory.weave, this is considered acceptable.
 
1301
        # - RBC 20060209
 
1302
        if revision_id is not None:
 
1303
            source_ids = self.source.get_ancestry(revision_id)
 
1304
            assert source_ids.pop(0) == None
 
1305
        else:
 
1306
            source_ids = self.source._all_possible_ids()
 
1307
        source_ids_set = set(source_ids)
 
1308
        # source_ids is the worst possible case we may need to pull.
 
1309
        # now we want to filter source_ids against what we actually
 
1310
        # have in target, but dont try to check for existence where we know
 
1311
        # we do not have a revision as that would be pointless.
 
1312
        target_ids = set(self.target._all_possible_ids())
 
1313
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
1314
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
1315
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
1316
        required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
 
1317
        if revision_id is not None:
 
1318
            # we used get_ancestry to determine source_ids then we are assured all
 
1319
            # revisions referenced are present as they are installed in topological order.
 
1320
            # and the tip revision was validated by get_ancestry.
 
1321
            return required_topo_revisions
 
1322
        else:
 
1323
            # if we just grabbed the possibly available ids, then 
 
1324
            # we only have an estimate of whats available and need to validate
 
1325
            # that against the revision records.
 
1326
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
 
1327
 
 
1328
 
 
1329
InterRepository.register_optimiser(InterWeaveRepo)
 
1330
 
 
1331
 
 
1332
class RepositoryTestProviderAdapter(object):
 
1333
    """A tool to generate a suite testing multiple repository formats at once.
 
1334
 
 
1335
    This is done by copying the test once for each transport and injecting
 
1336
    the transport_server, transport_readonly_server, and bzrdir_format and
 
1337
    repository_format classes into each copy. Each copy is also given a new id()
 
1338
    to make it easy to identify.
 
1339
    """
 
1340
 
 
1341
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1342
        self._transport_server = transport_server
 
1343
        self._transport_readonly_server = transport_readonly_server
 
1344
        self._formats = formats
 
1345
    
 
1346
    def adapt(self, test):
 
1347
        result = TestSuite()
 
1348
        for repository_format, bzrdir_format in self._formats:
 
1349
            new_test = deepcopy(test)
 
1350
            new_test.transport_server = self._transport_server
 
1351
            new_test.transport_readonly_server = self._transport_readonly_server
 
1352
            new_test.bzrdir_format = bzrdir_format
 
1353
            new_test.repository_format = repository_format
 
1354
            def make_new_test_id():
 
1355
                new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
 
1356
                return lambda: new_id
 
1357
            new_test.id = make_new_test_id()
 
1358
            result.addTest(new_test)
 
1359
        return result
 
1360
 
 
1361
 
 
1362
class InterRepositoryTestProviderAdapter(object):
 
1363
    """A tool to generate a suite testing multiple inter repository formats.
 
1364
 
 
1365
    This is done by copying the test once for each interrepo provider and injecting
 
1366
    the transport_server, transport_readonly_server, repository_format and 
 
1367
    repository_to_format classes into each copy.
 
1368
    Each copy is also given a new id() to make it easy to identify.
 
1369
    """
 
1370
 
 
1371
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1372
        self._transport_server = transport_server
 
1373
        self._transport_readonly_server = transport_readonly_server
 
1374
        self._formats = formats
 
1375
    
 
1376
    def adapt(self, test):
 
1377
        result = TestSuite()
 
1378
        for interrepo_class, repository_format, repository_format_to in self._formats:
 
1379
            new_test = deepcopy(test)
 
1380
            new_test.transport_server = self._transport_server
 
1381
            new_test.transport_readonly_server = self._transport_readonly_server
 
1382
            new_test.interrepo_class = interrepo_class
 
1383
            new_test.repository_format = repository_format
 
1384
            new_test.repository_format_to = repository_format_to
 
1385
            def make_new_test_id():
 
1386
                new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
 
1387
                return lambda: new_id
 
1388
            new_test.id = make_new_test_id()
 
1389
            result.addTest(new_test)
 
1390
        return result
 
1391
 
 
1392
    @staticmethod
 
1393
    def default_test_list():
 
1394
        """Generate the default list of interrepo permutations to test."""
 
1395
        result = []
 
1396
        # test the default InterRepository between format 6 and the current 
 
1397
        # default format.
 
1398
        # XXX: robertc 20060220 reinstate this when there are two supported
 
1399
        # formats which do not have an optimal code path between them.
 
1400
        result.append((InterRepository,
 
1401
                       RepositoryFormat6(),
 
1402
                       RepositoryFormatKnit1()))
 
1403
        for optimiser in InterRepository._optimisers:
 
1404
            result.append((optimiser,
 
1405
                           optimiser._matching_repo_format,
 
1406
                           optimiser._matching_repo_format
 
1407
                           ))
 
1408
        # if there are specific combinations we want to use, we can add them 
 
1409
        # here.
 
1410
        return result
 
1411
 
 
1412
 
 
1413
class CopyConverter(object):
 
1414
    """A repository conversion tool which just performs a copy of the content.
 
1415
    
 
1416
    This is slow but quite reliable.
 
1417
    """
 
1418
 
 
1419
    def __init__(self, target_format):
 
1420
        """Create a CopyConverter.
 
1421
 
 
1422
        :param target_format: The format the resulting repository should be.
 
1423
        """
 
1424
        self.target_format = target_format
 
1425
        
 
1426
    def convert(self, repo, pb):
 
1427
        """Perform the conversion of to_convert, giving feedback via pb.
 
1428
 
 
1429
        :param to_convert: The disk object to convert.
 
1430
        :param pb: a progress bar to use for progress information.
 
1431
        """
 
1432
        self.pb = pb
 
1433
        self.count = 0
 
1434
        self.total = 3
 
1435
        # this is only useful with metadir layouts - separated repo content.
 
1436
        # trigger an assertion if not such
 
1437
        repo._format.get_format_string()
 
1438
        self.repo_dir = repo.bzrdir
 
1439
        self.step('Moving repository to repository.backup')
 
1440
        self.repo_dir.transport.move('repository', 'repository.backup')
 
1441
        backup_transport =  self.repo_dir.transport.clone('repository.backup')
 
1442
        self.source_repo = repo._format.open(self.repo_dir,
 
1443
            _found=True,
 
1444
            _override_transport=backup_transport)
 
1445
        self.step('Creating new repository')
 
1446
        converted = self.target_format.initialize(self.repo_dir,
 
1447
                                                  self.source_repo.is_shared())
 
1448
        converted.lock_write()
 
1449
        try:
 
1450
            self.step('Copying content into repository.')
 
1451
            self.source_repo.copy_content_into(converted)
 
1452
        finally:
 
1453
            converted.unlock()
 
1454
        self.step('Deleting old repository content.')
 
1455
        self.repo_dir.transport.delete_tree('repository.backup')
 
1456
        self.pb.note('repository converted')
 
1457
 
 
1458
    def step(self, message):
 
1459
        """Update the pb by a step."""
 
1460
        self.count +=1
 
1461
        self.pb.update(message, self.count, self.total)