~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 07:14:10 UTC
  • mfrom: (1600 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1602.
  • Revision ID: mbp@sourcefrog.net-20060309071410-4ab7d54905541c75
[merge] from bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
from cStringIO import StringIO
19
19
from unittest import TestSuite
20
20
 
21
 
# FIXME: Pulling this in just for the unescape() routine seems like overkill.
22
 
import xml.sax.saxutils
23
 
 
24
21
import bzrlib.bzrdir as bzrdir
25
22
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
23
import bzrlib.errors as errors
26
24
from bzrlib.errors import InvalidRevisionId
 
25
import bzrlib.gpg as gpg
 
26
from bzrlib.graph import Graph
 
27
from bzrlib.inter import InterObject
 
28
from bzrlib.knit import KnitVersionedFile
27
29
from bzrlib.lockable_files import LockableFiles, TransportLock
28
30
from bzrlib.lockdir import LockDir
29
31
from bzrlib.osutils import safe_unicode
30
32
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
 
33
from bzrlib.store.versioned import VersionedFileStore, WeaveStore
35
34
from bzrlib.store.text import TextStore
36
35
from bzrlib.symbol_versioning import *
37
36
from bzrlib.trace import mutter
38
37
from bzrlib.tree import RevisionTree
 
38
from bzrlib.tsort import topo_sort
39
39
from bzrlib.testament import Testament
40
40
from bzrlib.tree import EmptyTree
41
41
import bzrlib.ui
 
42
from bzrlib.weave import WeaveFile
42
43
import bzrlib.xml5
43
44
 
44
45
 
65
66
        """
66
67
        inv_text = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
67
68
        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())
 
69
        inv_vf = self.control_weaves.get_weave('inventory',
 
70
                                               self.get_transaction())
 
71
        inv_vf.add_lines(revid, parents, bzrlib.osutils.split_lines(inv_text))
71
72
        return inv_sha1
72
73
 
73
74
    @needs_write_lock
95
96
            else:
96
97
                # yes, this is not suitable for adding with ghosts.
97
98
                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)
 
99
        self._revision_store.add_revision(rev, self.get_transaction())   
104
100
 
105
101
    @needs_read_lock
106
102
    def _all_possible_ids(self):
107
103
        """Return all the possible revisions that we could find."""
108
 
        return self.get_inventory_weave().names()
 
104
        return self.get_inventory_weave().versions()
109
105
 
110
106
    @needs_read_lock
111
107
    def all_revision_ids(self):
115
111
        present: for weaves ghosts may lead to a lack of correctness until
116
112
        the reweave updates the parents list.
117
113
        """
 
114
        if self._revision_store.text_store.listable():
 
115
            return self._revision_store.all_revision_ids(self.get_transaction())
118
116
        result = self._all_possible_ids()
119
117
        return self._eliminate_revisions_not_present(result)
120
118
 
135
133
        """Construct the current default format repository in a_bzrdir."""
136
134
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
137
135
 
138
 
    def __init__(self, _format, a_bzrdir, control_files, revision_store):
 
136
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
139
137
        """instantiate a Repository.
140
138
 
141
139
        :param _format: The format of the repository on disk.
150
148
        # the following are part of the public API for Repository:
151
149
        self.bzrdir = a_bzrdir
152
150
        self.control_files = control_files
153
 
        self.revision_store = revision_store
 
151
        self._revision_store = _revision_store
 
152
        self.text_store = text_store
 
153
        # backwards compatability
 
154
        self.weave_store = text_store
 
155
        # not right yet - should be more semantically clear ? 
 
156
        # 
 
157
        self.control_store = control_store
 
158
        self.control_weaves = control_store
154
159
 
155
160
    def lock_write(self):
156
161
        self.control_files.lock_write()
221
226
        self.copy_content_into(result, revision_id, basis)
222
227
        return result
223
228
 
 
229
    @needs_read_lock
224
230
    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()
 
231
        """True if this repository has a copy of the revision."""
 
232
        return self._revision_store.has_revision_id(revision_id,
 
233
                                                    self.get_transaction())
245
234
 
246
235
    @needs_read_lock
247
236
    def get_revision_reconcile(self, revision_id):
252
241
        be used by reconcile, or reconcile-alike commands that are correcting
253
242
        or testing the revision graph.
254
243
        """
255
 
        xml_file = self.get_revision_xml_file(revision_id)
 
244
        if not revision_id or not isinstance(revision_id, basestring):
 
245
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
 
246
        return self._revision_store.get_revision(revision_id,
 
247
                                                 self.get_transaction())
256
248
 
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
 
249
    @needs_read_lock
 
250
    def get_revision_xml(self, revision_id):
 
251
        rev = self.get_revision(revision_id) 
 
252
        rev_tmp = StringIO()
 
253
        # the current serializer..
 
254
        self._revision_store._serializer.write_revision(rev, rev_tmp)
 
255
        rev_tmp.seek(0)
 
256
        return rev_tmp.getvalue()
266
257
 
267
258
    @needs_read_lock
268
259
    def get_revision(self, revision_id):
286
277
        consistency and is only applicable to inventory-weave-for-ancestry
287
278
        using repository formats & fetchers.
288
279
        """
289
 
        weave_parents = inventory.parent_names(revision.revision_id)
290
 
        weave_names = inventory.names()
 
280
        weave_parents = inventory.get_parents(revision.revision_id)
 
281
        weave_names = inventory.versions()
291
282
        for parent_id in revision.parent_ids:
292
283
            if parent_id in weave_names:
293
284
                # this parent must not be a ghost.
295
286
                    # but it is a ghost
296
287
                    raise errors.CorruptRepository(self)
297
288
 
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
289
    @needs_write_lock
310
290
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
311
 
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
312
 
                                revision_id, "sig")
 
291
        signature = gpg_strategy.sign(plaintext)
 
292
        self._revision_store.add_revision_signature_text(revision_id,
 
293
                                                         signature,
 
294
                                                         self.get_transaction())
313
295
 
314
296
    def fileid_involved_between_revs(self, from_revid, to_revid):
315
297
        """Find file_id(s) which are involved in the changes between revisions.
328
310
        #       won't be fixed, because AD never saw revision C
329
311
        #       to cause a conflict which would force a reweave.
330
312
        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)
 
313
        from_set = set(w.get_ancestry(from_revid))
 
314
        to_set = set(w.get_ancestry(to_revid))
 
315
        changed = to_set.difference(from_set)
335
316
        return self._fileid_involved_by_set(changed)
336
317
 
337
318
    def fileid_involved(self, last_revid=None):
341
322
        """
342
323
        w = self.get_inventory_weave()
343
324
        if not last_revid:
344
 
            changed = set(w._names)
 
325
            changed = set(w.versions())
345
326
        else:
346
 
            included = w.inclusions([w.lookup(last_revid)])
347
 
            changed = map(w.idx_to_name, included)
 
327
            changed = set(w.get_ancestry(last_revid))
348
328
        return self._fileid_involved_by_set(changed)
349
329
 
350
330
    def fileid_involved_by_set(self, changes):
379
359
 
380
360
        w = self.get_inventory_weave()
381
361
        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
 
 
 
362
 
 
363
        for lineno, insert, deletes, line in w.walk(changes):
387
364
            start = line.find('file_id="')+9
388
365
            if start < 9: continue
389
366
            end = line.find('"', start)
390
367
            assert end>= 0
391
 
            file_id = xml.sax.saxutils.unescape(line[start:end])
 
368
            file_id = _unescape_xml(line[start:end])
392
369
 
393
370
            # check if file_id is already present
394
371
            if file_id in file_ids: continue
397
374
            if start < 10: continue
398
375
            end = line.find('"', start)
399
376
            assert end>= 0
400
 
            revision_id = xml.sax.saxutils.unescape(line[start:end])
401
 
 
 
377
            revision_id = _unescape_xml(line[start:end])
402
378
            if revision_id in changes:
403
379
                file_ids.add(file_id)
404
380
        return file_ids
420
396
        try:
421
397
            assert isinstance(revision_id, basestring), type(revision_id)
422
398
            iw = self.get_inventory_weave()
423
 
            return iw.get_text(iw.lookup(revision_id))
 
399
            return iw.get_text(revision_id)
424
400
        except IndexError:
425
401
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
426
402
 
431
407
        return self.get_revision(revision_id).inventory_sha1
432
408
 
433
409
    @needs_read_lock
 
410
    def get_revision_graph(self, revision_id=None):
 
411
        """Return a dictionary containing the revision graph.
 
412
        
 
413
        :return: a dictionary of revision_id->revision_parents_list.
 
414
        """
 
415
        weave = self.get_inventory_weave()
 
416
        all_revisions = self._eliminate_revisions_not_present(weave.versions())
 
417
        entire_graph = dict([(node, weave.get_parents(node)) for 
 
418
                             node in all_revisions])
 
419
        if revision_id is None:
 
420
            return entire_graph
 
421
        elif revision_id not in entire_graph:
 
422
            raise errors.NoSuchRevision(self, revision_id)
 
423
        else:
 
424
            # add what can be reached from revision_id
 
425
            result = {}
 
426
            pending = set([revision_id])
 
427
            while len(pending) > 0:
 
428
                node = pending.pop()
 
429
                result[node] = entire_graph[node]
 
430
                for revision_id in result[node]:
 
431
                    if revision_id not in result:
 
432
                        pending.add(revision_id)
 
433
            return result
 
434
 
 
435
    @needs_read_lock
 
436
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
437
        """Return a graph of the revisions with ghosts marked as applicable.
 
438
 
 
439
        :param revision_ids: an iterable of revisions to graph or None for all.
 
440
        :return: a Graph object with the graph reachable from revision_ids.
 
441
        """
 
442
        result = Graph()
 
443
        if not revision_ids:
 
444
            pending = set(self.all_revision_ids())
 
445
            required = set([])
 
446
        else:
 
447
            pending = set(revision_ids)
 
448
            required = set(revision_ids)
 
449
        done = set([])
 
450
        while len(pending):
 
451
            revision_id = pending.pop()
 
452
            try:
 
453
                rev = self.get_revision(revision_id)
 
454
            except errors.NoSuchRevision:
 
455
                if revision_id in required:
 
456
                    raise
 
457
                # a ghost
 
458
                result.add_ghost(revision_id)
 
459
                continue
 
460
            for parent_id in rev.parent_ids:
 
461
                # is this queued or done ?
 
462
                if (parent_id not in pending and
 
463
                    parent_id not in done):
 
464
                    # no, queue it.
 
465
                    pending.add(parent_id)
 
466
            result.add_node(revision_id, rev.parent_ids)
 
467
            done.add(result)
 
468
        return result
 
469
 
 
470
    @needs_read_lock
434
471
    def get_revision_inventory(self, revision_id):
435
472
        """Return inventory of a past revision."""
436
473
        # TODO: Unify this with get_inventory()
477
514
        if not self.has_revision(revision_id):
478
515
            raise errors.NoSuchRevision(self, revision_id)
479
516
        w = self.get_inventory_weave()
480
 
        return [None] + map(w.idx_to_name,
481
 
                            w.inclusions([w.lookup(revision_id)]))
 
517
        return [None] + w.get_ancestry(revision_id)
482
518
 
483
519
    @needs_read_lock
484
520
    def print_file(self, file, revision_id):
508
544
    def get_transaction(self):
509
545
        return self.control_files.get_transaction()
510
546
 
 
547
    def revision_parents(self, revid):
 
548
        return self.get_inventory_weave().parent_names(revid)
 
549
 
511
550
    @needs_write_lock
512
551
    def set_make_working_trees(self, new_value):
513
552
        """Set the policy flag for making working trees when creating branches.
545
584
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
546
585
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
547
586
 
 
587
    @needs_read_lock
 
588
    def has_signature_for_revision_id(self, revision_id):
 
589
        """Query for a revision signature for revision_id in the repository."""
 
590
        return self._revision_store.has_signature(revision_id,
 
591
                                                  self.get_transaction())
 
592
 
 
593
    @needs_read_lock
 
594
    def get_signature_text(self, revision_id):
 
595
        """Return the text for a signature."""
 
596
        return self._revision_store.get_signature_text(revision_id,
 
597
                                                       self.get_transaction())
 
598
 
548
599
 
549
600
class AllInOneRepository(Repository):
550
601
    """Legacy support - the repository behaviour for all-in-one branches."""
551
602
 
552
 
    def __init__(self, _format, a_bzrdir, revision_store):
 
603
    def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
553
604
        # we reuse one control files instance.
554
605
        dir_mode = a_bzrdir._control_files._dir_mode
555
606
        file_mode = a_bzrdir._control_files._file_mode
587
638
        # not broken out yet because the controlweaves|inventory_store
588
639
        # and text_store | weave_store bits are still different.
589
640
        if isinstance(_format, RepositoryFormat4):
 
641
            # cannot remove these - there is still no consistent api 
 
642
            # which allows access to this old info.
590
643
            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)
 
644
            text_store = get_store('text-store')
 
645
        super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
603
646
 
604
647
 
605
648
class MetaDirRepository(Repository):
606
649
    """Repositories in the new meta-dir layout."""
607
650
 
608
 
    def __init__(self, _format, a_bzrdir, control_files, revision_store):
 
651
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
609
652
        super(MetaDirRepository, self).__init__(_format,
610
653
                                                a_bzrdir,
611
654
                                                control_files,
612
 
                                                revision_store)
 
655
                                                _revision_store,
 
656
                                                control_store,
 
657
                                                text_store)
613
658
 
614
659
        dir_mode = self.control_files._dir_mode
615
660
        file_mode = self.control_files._file_mode
628
673
                ws.enable_cache = True
629
674
            return ws
630
675
 
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.')
 
676
 
 
677
class KnitRepository(MetaDirRepository):
 
678
    """Knit format repository."""
 
679
 
 
680
    @needs_read_lock
 
681
    def all_revision_ids(self):
 
682
        """See Repository.all_revision_ids()."""
 
683
        return self._revision_store.all_revision_ids(self.get_transaction())
640
684
 
641
685
 
642
686
class RepositoryFormat(object):
681
725
        except KeyError:
682
726
            raise errors.UnknownFormatError(format_string)
683
727
 
 
728
    def _get_control_store(self, repo_transport, control_files):
 
729
        """Return the control store for this repository."""
 
730
        raise NotImplementedError(self._get_control_store)
 
731
    
684
732
    @classmethod
685
733
    def get_default_format(klass):
686
734
        """Return the current default format."""
698
746
        """Return the revision store object for this a_bzrdir."""
699
747
        raise NotImplementedError(self._get_revision_store)
700
748
 
701
 
    def _get_rev_store(self,
702
 
                   transport,
703
 
                   control_files,
704
 
                   name,
705
 
                   compressed=True,
706
 
                   prefixed=False):
 
749
    def _get_text_rev_store(self,
 
750
                            transport,
 
751
                            control_files,
 
752
                            name,
 
753
                            compressed=True,
 
754
                            prefixed=False,
 
755
                            serializer=None):
707
756
        """Common logic for getting a revision store for a repository.
708
757
        
709
 
        see self._get_revision_store for the method to 
 
758
        see self._get_revision_store for the subclass-overridable method to 
710
759
        get the store for a repository.
711
760
        """
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
 
761
        from bzrlib.store.revision.text import TextRevisionStore
 
762
        dir_mode = control_files._dir_mode
 
763
        file_mode = control_files._file_mode
 
764
        text_store =TextStore(transport.clone(name),
 
765
                              prefixed=prefixed,
 
766
                              compressed=compressed,
 
767
                              dir_mode=dir_mode,
 
768
                              file_mode=file_mode)
 
769
        _revision_store = TextRevisionStore(text_store, serializer)
 
770
        return _revision_store
 
771
 
 
772
    def _get_versioned_file_store(self,
 
773
                                  name,
 
774
                                  transport,
 
775
                                  control_files,
 
776
                                  prefixed=True,
 
777
                                  versionedfile_class=WeaveFile):
 
778
        weave_transport = control_files._transport.clone(name)
 
779
        dir_mode = control_files._dir_mode
 
780
        file_mode = control_files._file_mode
 
781
        return VersionedFileStore(weave_transport, prefixed=prefixed,
 
782
                                dir_mode=dir_mode,
 
783
                                file_mode=file_mode,
 
784
                                versionedfile_class=versionedfile_class)
725
785
 
726
786
    def initialize(self, a_bzrdir, shared=False):
727
787
        """Initialize a repository of this format in a_bzrdir.
807
867
            control_files.unlock()
808
868
        return self.open(a_bzrdir, _found=True)
809
869
 
 
870
    def _get_control_store(self, repo_transport, control_files):
 
871
        """Return the control store for this repository."""
 
872
        return self._get_versioned_file_store('',
 
873
                                              repo_transport,
 
874
                                              control_files,
 
875
                                              prefixed=False)
 
876
 
 
877
    def _get_text_store(self, transport, control_files):
 
878
        """Get a store for file texts for this format."""
 
879
        raise NotImplementedError(self._get_text_store)
 
880
 
810
881
    def open(self, a_bzrdir, _found=False):
811
882
        """See RepositoryFormat.open()."""
812
883
        if not _found:
815
886
 
816
887
        repo_transport = a_bzrdir.get_repository_transport(None)
817
888
        control_files = a_bzrdir._control_files
818
 
        revision_store = self._get_revision_store(repo_transport, control_files)
 
889
        text_store = self._get_text_store(repo_transport, control_files)
 
890
        control_store = self._get_control_store(repo_transport, control_files)
 
891
        _revision_store = self._get_revision_store(repo_transport, control_files)
819
892
        return AllInOneRepository(_format=self,
820
893
                                  a_bzrdir=a_bzrdir,
821
 
                                  revision_store=revision_store)
 
894
                                  _revision_store=_revision_store,
 
895
                                  control_store=control_store,
 
896
                                  text_store=text_store)
822
897
 
823
898
 
824
899
class RepositoryFormat4(PreSplitOutRepositoryFormat):
850
925
        """
851
926
        return False
852
927
 
 
928
    def _get_control_store(self, repo_transport, control_files):
 
929
        """Format 4 repositories have no formal control store at this point.
 
930
        
 
931
        This will cause any control-file-needing apis to fail - this is desired.
 
932
        """
 
933
        return None
 
934
    
853
935
    def _get_revision_store(self, repo_transport, control_files):
854
936
        """See RepositoryFormat._get_revision_store()."""
855
 
        return self._get_rev_store(repo_transport,
856
 
                                   control_files,
857
 
                                   'revision-store')
 
937
        from bzrlib.xml4 import serializer_v4
 
938
        return self._get_text_rev_store(repo_transport,
 
939
                                        control_files,
 
940
                                        'revision-store',
 
941
                                        serializer=serializer_v4)
 
942
 
 
943
    def _get_text_store(self, transport, control_files):
 
944
        """See RepositoryFormat._get_text_store()."""
858
945
 
859
946
 
860
947
class RepositoryFormat5(PreSplitOutRepositoryFormat):
873
960
    def _get_revision_store(self, repo_transport, control_files):
874
961
        """See RepositoryFormat._get_revision_store()."""
875
962
        """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)
 
963
        return self._get_text_rev_store(repo_transport,
 
964
                                        control_files,
 
965
                                        'revision-store',
 
966
                                        compressed=False)
 
967
 
 
968
    def _get_text_store(self, transport, control_files):
 
969
        """See RepositoryFormat._get_text_store()."""
 
970
        return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
880
971
 
881
972
 
882
973
class RepositoryFormat6(PreSplitOutRepositoryFormat):
894
985
 
895
986
    def _get_revision_store(self, repo_transport, control_files):
896
987
        """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)
 
988
        return self._get_text_rev_store(repo_transport,
 
989
                                        control_files,
 
990
                                        'revision-store',
 
991
                                        compressed=False,
 
992
                                        prefixed=True)
 
993
 
 
994
    def _get_text_store(self, transport, control_files):
 
995
        """See RepositoryFormat._get_text_store()."""
 
996
        return self._get_versioned_file_store('weaves', transport, control_files)
902
997
 
903
998
 
904
999
class MetaDirRepositoryFormat(RepositoryFormat):
917
1012
        control_files.create_lock()
918
1013
        return control_files
919
1014
 
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
1015
    def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
951
1016
        """Upload the initial blank content."""
952
1017
        control_files = self._create_control_files(a_bzrdir)
976
1041
     - an optional 'no-working-trees' flag
977
1042
    """
978
1043
 
 
1044
    def _get_control_store(self, repo_transport, control_files):
 
1045
        """Return the control store for this repository."""
 
1046
        return self._get_versioned_file_store('',
 
1047
                                              repo_transport,
 
1048
                                              control_files,
 
1049
                                              prefixed=False)
 
1050
 
979
1051
    def get_format_string(self):
980
1052
        """See RepositoryFormat.get_format_string()."""
981
1053
        return "Bazaar-NG Repository format 7"
982
1054
 
 
1055
    def _get_revision_store(self, repo_transport, control_files):
 
1056
        """See RepositoryFormat._get_revision_store()."""
 
1057
        return self._get_text_rev_store(repo_transport,
 
1058
                                        control_files,
 
1059
                                        'revision-store',
 
1060
                                        compressed=False,
 
1061
                                        prefixed=True,
 
1062
                                        )
 
1063
 
 
1064
    def _get_text_store(self, transport, control_files):
 
1065
        """See RepositoryFormat._get_text_store()."""
 
1066
        return self._get_versioned_file_store('weaves',
 
1067
                                              transport,
 
1068
                                              control_files)
 
1069
 
983
1070
    def initialize(self, a_bzrdir, shared=False):
984
1071
        """Create a weave repository.
985
1072
 
1003
1090
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
1004
1091
        return self.open(a_bzrdir=a_bzrdir, _found=True)
1005
1092
 
 
1093
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1094
        """See RepositoryFormat.open().
 
1095
        
 
1096
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1097
                                    repository at a slightly different url
 
1098
                                    than normal. I.e. during 'upgrade'.
 
1099
        """
 
1100
        if not _found:
 
1101
            format = RepositoryFormat.find_format(a_bzrdir)
 
1102
            assert format.__class__ ==  self.__class__
 
1103
        if _override_transport is not None:
 
1104
            repo_transport = _override_transport
 
1105
        else:
 
1106
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1107
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
 
1108
        text_store = self._get_text_store(repo_transport, control_files)
 
1109
        control_store = self._get_control_store(repo_transport, control_files)
 
1110
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1111
        return MetaDirRepository(_format=self,
 
1112
                                 a_bzrdir=a_bzrdir,
 
1113
                                 control_files=control_files,
 
1114
                                 _revision_store=_revision_store,
 
1115
                                 control_store=control_store,
 
1116
                                 text_store=text_store)
 
1117
 
1006
1118
 
1007
1119
class RepositoryFormatKnit1(MetaDirRepositoryFormat):
1008
1120
    """Bzr repository knit format 1.
1018
1130
     - a LockDir lock
1019
1131
    """
1020
1132
 
 
1133
    def _get_control_store(self, repo_transport, control_files):
 
1134
        """Return the control store for this repository."""
 
1135
        return self._get_versioned_file_store('',
 
1136
                                              repo_transport,
 
1137
                                              control_files,
 
1138
                                              prefixed=False,
 
1139
                                              versionedfile_class=KnitVersionedFile)
 
1140
 
1021
1141
    def get_format_string(self):
1022
1142
        """See RepositoryFormat.get_format_string()."""
1023
1143
        return "Bazaar-NG Knit Repository Format 1"
1024
1144
 
 
1145
    def _get_revision_store(self, repo_transport, control_files):
 
1146
        """See RepositoryFormat._get_revision_store()."""
 
1147
        from bzrlib.store.revision.knit import KnitRevisionStore
 
1148
        versioned_file_store = VersionedFileStore(
 
1149
            repo_transport,
 
1150
            file_mode = control_files._file_mode,
 
1151
            prefixed=False,
 
1152
            precious=True,
 
1153
            versionedfile_class=KnitVersionedFile)
 
1154
        return KnitRevisionStore(versioned_file_store)
 
1155
 
 
1156
    def _get_text_store(self, transport, control_files):
 
1157
        """See RepositoryFormat._get_text_store()."""
 
1158
        return self._get_versioned_file_store('knits',
 
1159
                                              transport,
 
1160
                                              control_files,
 
1161
                                              versionedfile_class=KnitVersionedFile)
 
1162
 
1025
1163
    def initialize(self, a_bzrdir, shared=False):
1026
1164
        """Create a knit format 1 repository.
1027
1165
 
1039
1177
        empty_weave = sio.getvalue()
1040
1178
 
1041
1179
        mutter('creating repository in %s.', a_bzrdir.transport.base)
1042
 
        dirs = ['revision-store', 'knits']
1043
 
        files = [('inventory.weave', StringIO(empty_weave)), 
 
1180
        dirs = ['revision-store', 'knits', 'control']
 
1181
        files = [('control/inventory.weave', StringIO(empty_weave)), 
1044
1182
                 ]
1045
1183
        utf8_files = [('format', self.get_format_string())]
1046
1184
        
1047
1185
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1186
        repo_transport = a_bzrdir.get_repository_transport(None)
 
1187
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
 
1188
        control_store = self._get_control_store(repo_transport, control_files)
 
1189
        transaction = bzrlib.transactions.PassThroughTransaction()
 
1190
        # trigger a write of the inventory store.
 
1191
        control_store.get_weave_or_empty('inventory', transaction)
 
1192
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1193
        _revision_store.has_revision_id('A', transaction)
 
1194
        _revision_store.get_signature_file(transaction)
1048
1195
        return self.open(a_bzrdir=a_bzrdir, _found=True)
1049
1196
 
 
1197
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1198
        """See RepositoryFormat.open().
 
1199
        
 
1200
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1201
                                    repository at a slightly different url
 
1202
                                    than normal. I.e. during 'upgrade'.
 
1203
        """
 
1204
        if not _found:
 
1205
            format = RepositoryFormat.find_format(a_bzrdir)
 
1206
            assert format.__class__ ==  self.__class__
 
1207
        if _override_transport is not None:
 
1208
            repo_transport = _override_transport
 
1209
        else:
 
1210
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1211
        control_files = LockableFiles(repo_transport, 'lock', LockDir)
 
1212
        text_store = self._get_text_store(repo_transport, control_files)
 
1213
        control_store = self._get_control_store(repo_transport, control_files)
 
1214
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1215
        return KnitRepository(_format=self,
 
1216
                              a_bzrdir=a_bzrdir,
 
1217
                              control_files=control_files,
 
1218
                              _revision_store=_revision_store,
 
1219
                              control_store=control_store,
 
1220
                              text_store=text_store)
 
1221
 
1050
1222
 
1051
1223
# formats which have no format string are not discoverable
1052
1224
# and not independently creatable, so are not registered.
1059
1231
                   RepositoryFormat6()]
1060
1232
 
1061
1233
 
1062
 
class InterRepository(object):
 
1234
class InterRepository(InterObject):
1063
1235
    """This class represents operations taking place between two repositories.
1064
1236
 
1065
1237
    Its instances have methods like copy_content and fetch, and contain
1070
1242
    operations with another repository - they will always forward to
1071
1243
    InterRepository.get(other).method_name(parameters).
1072
1244
    """
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
1245
 
1078
1246
    _optimisers = set()
1079
1247
    """The available optimised InterRepository types."""
1080
1248
 
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
1249
    @needs_write_lock
1092
1250
    def copy_content(self, revision_id=None, basis=None):
1093
1251
        """Make a complete copy of the content in self into destination.
1106
1264
        # grab the basis available data
1107
1265
        if basis is not None:
1108
1266
            self.target.fetch(basis, revision_id=revision_id)
1109
 
        # but dont both fetching if we have the needed data now.
 
1267
        # but dont bother fetching if we have the needed data now.
1110
1268
        if (revision_id not in (None, NULL_REVISION) and 
1111
1269
            self.target.has_revision(revision_id)):
1112
1270
            return
1137
1295
        Returns the copied revision count and the failed revisions in a tuple:
1138
1296
        (copied, failures).
1139
1297
        """
1140
 
        from bzrlib.fetch import RepoFetcher
 
1298
        from bzrlib.fetch import GenericRepoFetcher
1141
1299
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1142
1300
               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)
 
1301
        f = GenericRepoFetcher(to_repository=self.target,
 
1302
                               from_repository=self.source,
 
1303
                               last_revision=revision_id,
 
1304
                               pb=pb)
1147
1305
        return f.count_copied, f.failed_revisions
1148
1306
 
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
1307
    def lock_read(self):
1166
1308
        """Take out a logical read lock.
1167
1309
 
1200
1342
        # that we've decided we need.
1201
1343
        return [rev_id for rev_id in source_ids if rev_id in result_set]
1202
1344
 
1203
 
    @classmethod
1204
 
    def register_optimiser(klass, optimiser):
1205
 
        """Register an InterRepository optimiser."""
1206
 
        klass._optimisers.add(optimiser)
1207
 
 
1208
1345
    def unlock(self):
1209
1346
        """Release the locks on source and target."""
1210
1347
        try:
1212
1349
        finally:
1213
1350
            self.source.unlock()
1214
1351
 
1215
 
    @classmethod
1216
 
    def unregister_optimiser(klass, optimiser):
1217
 
        """Unregister an InterRepository optimiser."""
1218
 
        klass._optimisers.remove(optimiser)
1219
 
 
1220
1352
 
1221
1353
class InterWeaveRepo(InterRepository):
1222
1354
    """Optimised code paths between Weave based repositories."""
1262
1394
                pass
1263
1395
            # FIXME do not peek!
1264
1396
            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)
 
1397
                pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
1398
                try:
 
1399
                    self.target.weave_store.copy_all_ids(
 
1400
                        self.source.weave_store,
 
1401
                        pb=pb,
 
1402
                        from_transaction=self.source.get_transaction(),
 
1403
                        to_transaction=self.target.get_transaction())
 
1404
                    pb.update('copying inventory', 0, 1)
 
1405
                    self.target.control_weaves.copy_multi(
 
1406
                        self.source.control_weaves, ['inventory'],
 
1407
                        from_transaction=self.source.get_transaction(),
 
1408
                        to_transaction=self.target.get_transaction())
 
1409
                    self.target._revision_store.text_store.copy_all_ids(
 
1410
                        self.source._revision_store.text_store,
 
1411
                        pb=pb)
 
1412
                finally:
 
1413
                    pb.finished()
1273
1414
            else:
1274
1415
                self.target.fetch(self.source, revision_id=revision_id)
1275
1416
 
1276
1417
    @needs_write_lock
1277
1418
    def fetch(self, revision_id=None, pb=None):
1278
1419
        """See InterRepository.fetch()."""
1279
 
        from bzrlib.fetch import RepoFetcher
 
1420
        from bzrlib.fetch import GenericRepoFetcher
1280
1421
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
1281
1422
               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)
 
1423
        f = GenericRepoFetcher(to_repository=self.target,
 
1424
                               from_repository=self.source,
 
1425
                               last_revision=revision_id,
 
1426
                               pb=pb)
1286
1427
        return f.count_copied, f.failed_revisions
1287
1428
 
1288
1429
    @needs_read_lock
1326
1467
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
1327
1468
 
1328
1469
 
 
1470
class InterKnitRepo(InterRepository):
 
1471
    """Optimised code paths between Knit based repositories."""
 
1472
 
 
1473
    _matching_repo_format = RepositoryFormatKnit1()
 
1474
    """Repository format for testing with."""
 
1475
 
 
1476
    @staticmethod
 
1477
    def is_compatible(source, target):
 
1478
        """Be compatible with known Knit formats.
 
1479
        
 
1480
        We dont test for the stores being of specific types becase that
 
1481
        could lead to confusing results, and there is no need to be 
 
1482
        overly general.
 
1483
        """
 
1484
        try:
 
1485
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
 
1486
                    isinstance(target._format, (RepositoryFormatKnit1)))
 
1487
        except AttributeError:
 
1488
            return False
 
1489
 
 
1490
    @needs_write_lock
 
1491
    def fetch(self, revision_id=None, pb=None):
 
1492
        """See InterRepository.fetch()."""
 
1493
        from bzrlib.fetch import KnitRepoFetcher
 
1494
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
1495
               self.source, self.source._format, self.target, self.target._format)
 
1496
        f = KnitRepoFetcher(to_repository=self.target,
 
1497
                            from_repository=self.source,
 
1498
                            last_revision=revision_id,
 
1499
                            pb=pb)
 
1500
        return f.count_copied, f.failed_revisions
 
1501
 
 
1502
    @needs_read_lock
 
1503
    def missing_revision_ids(self, revision_id=None):
 
1504
        """See InterRepository.missing_revision_ids()."""
 
1505
        if revision_id is not None:
 
1506
            source_ids = self.source.get_ancestry(revision_id)
 
1507
            assert source_ids.pop(0) == None
 
1508
        else:
 
1509
            source_ids = self.source._all_possible_ids()
 
1510
        source_ids_set = set(source_ids)
 
1511
        # source_ids is the worst possible case we may need to pull.
 
1512
        # now we want to filter source_ids against what we actually
 
1513
        # have in target, but dont try to check for existence where we know
 
1514
        # we do not have a revision as that would be pointless.
 
1515
        target_ids = set(self.target._all_possible_ids())
 
1516
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
1517
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
1518
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
1519
        required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
 
1520
        if revision_id is not None:
 
1521
            # we used get_ancestry to determine source_ids then we are assured all
 
1522
            # revisions referenced are present as they are installed in topological order.
 
1523
            # and the tip revision was validated by get_ancestry.
 
1524
            return required_topo_revisions
 
1525
        else:
 
1526
            # if we just grabbed the possibly available ids, then 
 
1527
            # we only have an estimate of whats available and need to validate
 
1528
            # that against the revision records.
 
1529
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
 
1530
 
1329
1531
InterRepository.register_optimiser(InterWeaveRepo)
 
1532
InterRepository.register_optimiser(InterKnitRepo)
1330
1533
 
1331
1534
 
1332
1535
class RepositoryTestProviderAdapter(object):
1459
1662
        """Update the pb by a step."""
1460
1663
        self.count +=1
1461
1664
        self.pb.update(message, self.count, self.total)
 
1665
 
 
1666
 
 
1667
# Copied from xml.sax.saxutils
 
1668
def _unescape_xml(data):
 
1669
    """Unescape &amp;, &lt;, and &gt; in a string of data.
 
1670
    """
 
1671
    data = data.replace("&lt;", "<")
 
1672
    data = data.replace("&gt;", ">")
 
1673
    # must do ampersand last
 
1674
    return data.replace("&amp;", "&")