/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Martin Pool
  • Date: 2007-01-19 02:46:50 UTC
  • mto: (2220.2.19 tags)
  • mto: This revision was merged to the branch mainline in revision 2309.
  • Revision ID: mbp@sourcefrog.net-20070119024650-o12ekgnjski2v3ed
Add tag: revision namespace.

Change make_tag to set_tag

Add Branch.lookup_tag (defers to Branch.lookup_tag) for simplicity of callers,
and possibly to help formats where it's better to look up by branch.

Factor out RevisionInfo.from_revision_id

Separate tag storage into a _TagStore strategy within the Repository, 
so they can be reused across formats.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 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 cStringIO import StringIO
 
18
 
 
19
from bzrlib.lazy_import import lazy_import
 
20
lazy_import(globals(), """
 
21
from binascii import hexlify
 
22
from copy import deepcopy
 
23
import re
 
24
import time
 
25
import unittest
 
26
 
 
27
from bzrlib import (
 
28
    bzrdir,
 
29
    check,
 
30
    delta,
 
31
    errors,
 
32
    generate_ids,
 
33
    gpg,
 
34
    graph,
 
35
    knit,
 
36
    lazy_regex,
 
37
    lockable_files,
 
38
    lockdir,
 
39
    osutils,
 
40
    revision as _mod_revision,
 
41
    symbol_versioning,
 
42
    transactions,
 
43
    ui,
 
44
    weave,
 
45
    weavefile,
 
46
    xml5,
 
47
    xml6,
 
48
    )
 
49
from bzrlib.osutils import (
 
50
    rand_bytes,
 
51
    compact_date, 
 
52
    local_time_offset,
 
53
    )
 
54
from bzrlib.revisiontree import RevisionTree
 
55
from bzrlib.store.versioned import VersionedFileStore
 
56
from bzrlib.store.text import TextStore
 
57
from bzrlib.testament import Testament
 
58
""")
 
59
 
 
60
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
61
from bzrlib.inter import InterObject
 
62
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
 
63
from bzrlib.symbol_versioning import (
 
64
        deprecated_method,
 
65
        zero_nine,
 
66
        )
 
67
from bzrlib.trace import mutter, note, warning
 
68
 
 
69
 
 
70
# Old formats display a warning, but only once
 
71
_deprecation_warning_done = False
 
72
 
 
73
 
 
74
######################################################################
 
75
# tag storage
 
76
 
 
77
 
 
78
class _TagStore(object):
 
79
    def __init__(self, repository):
 
80
        self.repository = repository
 
81
 
 
82
class _DisabledTagStore(_TagStore):
 
83
    """Tag storage that refuses to store anything.
 
84
 
 
85
    This is used by older formats that can't store tags.
 
86
    """
 
87
 
 
88
    def _not_supported(self, *a, **k):
 
89
        raise errors.TagsNotSupported(self.repository)
 
90
 
 
91
    set_tag = _not_supported
 
92
 
 
93
 
 
94
class _BasicTagStore(_TagStore):
 
95
    """Tag storage in an unversioned repository control file.
 
96
    """
 
97
 
 
98
    def set_tag(self, tag_name, tag_target):
 
99
        """Add a tag definition to the repository.
 
100
 
 
101
        Behaviour if the tag is already present is not defined (yet).
 
102
        """
 
103
        # all done with a write lock held, so this looks atomic
 
104
        self.repository.lock_write()
 
105
        try:
 
106
            td = self.get_tag_dict()
 
107
            td[tag_name] = tag_target
 
108
            self._set_tag_dict(td)
 
109
        finally:
 
110
            self.repository.unlock()
 
111
 
 
112
    def lookup_tag(self, tag_name):
 
113
        """Return the referent string of a tag"""
 
114
        td = self.get_tag_dict()
 
115
        try:
 
116
            return td[tag_name]
 
117
        except KeyError:
 
118
            raise errors.NoSuchTag(tag_name)
 
119
 
 
120
    def get_tag_dict(self):
 
121
        self.repository.lock_read()
 
122
        try:
 
123
            tag_content = self.repository.control_files.get_utf8('tags').read()
 
124
            return self._deserialize_tag_dict(tag_content)
 
125
        finally:
 
126
            self.repository.unlock()
 
127
 
 
128
    def _set_tag_dict(self, new_dict):
 
129
        """Replace all tag definitions
 
130
 
 
131
        :param new_dict: Dictionary from tag name to target.
 
132
        """
 
133
        self.repository.lock_read()
 
134
        try:
 
135
            self.repository.control_files.put_utf8('tags',
 
136
                self._serialize_tag_dict(new_dict))
 
137
        finally:
 
138
            self.repository.unlock()
 
139
 
 
140
    def _serialize_tag_dict(self, tag_dict):
 
141
        s = []
 
142
        for tag, target in sorted(tag_dict.items()):
 
143
            # TODO: check that tag names and targets are acceptable
 
144
            s.append(tag + '\t' + target + '\n')
 
145
        return ''.join(s)
 
146
 
 
147
    def _deserialize_tag_dict(self, tag_content):
 
148
        """Convert the tag file into a dictionary of tags"""
 
149
        d = {}
 
150
        for l in tag_content.splitlines():
 
151
            tag, target = l.split('\t', 1)
 
152
            d[tag] = target
 
153
        return d
 
154
 
 
155
 
 
156
######################################################################
 
157
# Repositories
 
158
 
 
159
class Repository(object):
 
160
    """Repository holding history for one or more branches.
 
161
 
 
162
    The repository holds and retrieves historical information including
 
163
    revisions and file history.  It's normally accessed only by the Branch,
 
164
    which views a particular line of development through that history.
 
165
 
 
166
    The Repository builds on top of Stores and a Transport, which respectively 
 
167
    describe the disk data format and the way of accessing the (possibly 
 
168
    remote) disk.
 
169
    """
 
170
 
 
171
    # override this to set the strategy for storing tags
 
172
    _tag_store_class = _DisabledTagStore
 
173
 
 
174
    _file_ids_altered_regex = lazy_regex.lazy_compile(
 
175
        r'file_id="(?P<file_id>[^"]+)"'
 
176
        r'.*revision="(?P<revision_id>[^"]+)"'
 
177
        )
 
178
 
 
179
    @needs_write_lock
 
180
    def add_inventory(self, revid, inv, parents):
 
181
        """Add the inventory inv to the repository as revid.
 
182
        
 
183
        :param parents: The revision ids of the parents that revid
 
184
                        is known to have and are in the repository already.
 
185
 
 
186
        returns the sha1 of the serialized inventory.
 
187
        """
 
188
        assert inv.revision_id is None or inv.revision_id == revid, \
 
189
            "Mismatch between inventory revision" \
 
190
            " id and insertion revid (%r, %r)" % (inv.revision_id, revid)
 
191
        assert inv.root is not None
 
192
        inv_text = self.serialise_inventory(inv)
 
193
        inv_sha1 = osutils.sha_string(inv_text)
 
194
        inv_vf = self.control_weaves.get_weave('inventory',
 
195
                                               self.get_transaction())
 
196
        self._inventory_add_lines(inv_vf, revid, parents, osutils.split_lines(inv_text))
 
197
        return inv_sha1
 
198
 
 
199
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
 
200
        final_parents = []
 
201
        for parent in parents:
 
202
            if parent in inv_vf:
 
203
                final_parents.append(parent)
 
204
 
 
205
        inv_vf.add_lines(revid, final_parents, lines)
 
206
 
 
207
    @needs_write_lock
 
208
    def add_revision(self, rev_id, rev, inv=None, config=None):
 
209
        """Add rev to the revision store as rev_id.
 
210
 
 
211
        :param rev_id: the revision id to use.
 
212
        :param rev: The revision object.
 
213
        :param inv: The inventory for the revision. if None, it will be looked
 
214
                    up in the inventory storer
 
215
        :param config: If None no digital signature will be created.
 
216
                       If supplied its signature_needed method will be used
 
217
                       to determine if a signature should be made.
 
218
        """
 
219
        if config is not None and config.signature_needed():
 
220
            if inv is None:
 
221
                inv = self.get_inventory(rev_id)
 
222
            plaintext = Testament(rev, inv).as_short_text()
 
223
            self.store_revision_signature(
 
224
                gpg.GPGStrategy(config), plaintext, rev_id)
 
225
        if not rev_id in self.get_inventory_weave():
 
226
            if inv is None:
 
227
                raise errors.WeaveRevisionNotPresent(rev_id,
 
228
                                                     self.get_inventory_weave())
 
229
            else:
 
230
                # yes, this is not suitable for adding with ghosts.
 
231
                self.add_inventory(rev_id, inv, rev.parent_ids)
 
232
        self._revision_store.add_revision(rev, self.get_transaction())
 
233
 
 
234
    @needs_read_lock
 
235
    def _all_possible_ids(self):
 
236
        """Return all the possible revisions that we could find."""
 
237
        return self.get_inventory_weave().versions()
 
238
 
 
239
    def all_revision_ids(self):
 
240
        """Returns a list of all the revision ids in the repository. 
 
241
 
 
242
        This is deprecated because code should generally work on the graph
 
243
        reachable from a particular revision, and ignore any other revisions
 
244
        that might be present.  There is no direct replacement method.
 
245
        """
 
246
        return self._all_revision_ids()
 
247
 
 
248
    @needs_read_lock
 
249
    def _all_revision_ids(self):
 
250
        """Returns a list of all the revision ids in the repository. 
 
251
 
 
252
        These are in as much topological order as the underlying store can 
 
253
        present: for weaves ghosts may lead to a lack of correctness until
 
254
        the reweave updates the parents list.
 
255
        """
 
256
        if self._revision_store.text_store.listable():
 
257
            return self._revision_store.all_revision_ids(self.get_transaction())
 
258
        result = self._all_possible_ids()
 
259
        return self._eliminate_revisions_not_present(result)
 
260
 
 
261
    def break_lock(self):
 
262
        """Break a lock if one is present from another instance.
 
263
 
 
264
        Uses the ui factory to ask for confirmation if the lock may be from
 
265
        an active process.
 
266
        """
 
267
        self.control_files.break_lock()
 
268
 
 
269
    @needs_read_lock
 
270
    def _eliminate_revisions_not_present(self, revision_ids):
 
271
        """Check every revision id in revision_ids to see if we have it.
 
272
 
 
273
        Returns a set of the present revisions.
 
274
        """
 
275
        result = []
 
276
        for id in revision_ids:
 
277
            if self.has_revision(id):
 
278
               result.append(id)
 
279
        return result
 
280
 
 
281
    @staticmethod
 
282
    def create(a_bzrdir):
 
283
        """Construct the current default format repository in a_bzrdir."""
 
284
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
 
285
 
 
286
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
287
        """instantiate a Repository.
 
288
 
 
289
        :param _format: The format of the repository on disk.
 
290
        :param a_bzrdir: The BzrDir of the repository.
 
291
 
 
292
        In the future we will have a single api for all stores for
 
293
        getting file texts, inventories and revisions, then
 
294
        this construct will accept instances of those things.
 
295
        """
 
296
        super(Repository, self).__init__()
 
297
        self._format = _format
 
298
        # the following are part of the public API for Repository:
 
299
        self.bzrdir = a_bzrdir
 
300
        self.control_files = control_files
 
301
        self._revision_store = _revision_store
 
302
        self.text_store = text_store
 
303
        # backwards compatibility
 
304
        self.weave_store = text_store
 
305
        # not right yet - should be more semantically clear ? 
 
306
        # 
 
307
        self.control_store = control_store
 
308
        self.control_weaves = control_store
 
309
        # TODO: make sure to construct the right store classes, etc, depending
 
310
        # on whether escaping is required.
 
311
        self._warn_if_deprecated()
 
312
        self._serializer = xml5.serializer_v5
 
313
        self._tag_store = self._tag_store_class(self)
 
314
 
 
315
    def __repr__(self):
 
316
        return '%s(%r)' % (self.__class__.__name__, 
 
317
                           self.bzrdir.transport.base)
 
318
 
 
319
    def is_locked(self):
 
320
        return self.control_files.is_locked()
 
321
 
 
322
    def lock_write(self):
 
323
        self.control_files.lock_write()
 
324
 
 
325
    def lock_read(self):
 
326
        self.control_files.lock_read()
 
327
 
 
328
    def get_physical_lock_status(self):
 
329
        return self.control_files.get_physical_lock_status()
 
330
 
 
331
    @needs_read_lock
 
332
    def missing_revision_ids(self, other, revision_id=None):
 
333
        """Return the revision ids that other has that this does not.
 
334
        
 
335
        These are returned in topological order.
 
336
 
 
337
        revision_id: only return revision ids included by revision_id.
 
338
        """
 
339
        return InterRepository.get(other, self).missing_revision_ids(revision_id)
 
340
 
 
341
    @staticmethod
 
342
    def open(base):
 
343
        """Open the repository rooted at base.
 
344
 
 
345
        For instance, if the repository is at URL/.bzr/repository,
 
346
        Repository.open(URL) -> a Repository instance.
 
347
        """
 
348
        control = bzrdir.BzrDir.open(base)
 
349
        return control.open_repository()
 
350
 
 
351
    def copy_content_into(self, destination, revision_id=None, basis=None):
 
352
        """Make a complete copy of the content in self into destination.
 
353
        
 
354
        This is a destructive operation! Do not use it on existing 
 
355
        repositories.
 
356
        """
 
357
        return InterRepository.get(self, destination).copy_content(revision_id, basis)
 
358
 
 
359
    def fetch(self, source, revision_id=None, pb=None):
 
360
        """Fetch the content required to construct revision_id from source.
 
361
 
 
362
        If revision_id is None all content is copied.
 
363
        """
 
364
        return InterRepository.get(source, self).fetch(revision_id=revision_id,
 
365
                                                       pb=pb)
 
366
 
 
367
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
 
368
                           timezone=None, committer=None, revprops=None, 
 
369
                           revision_id=None):
 
370
        """Obtain a CommitBuilder for this repository.
 
371
        
 
372
        :param branch: Branch to commit to.
 
373
        :param parents: Revision ids of the parents of the new revision.
 
374
        :param config: Configuration to use.
 
375
        :param timestamp: Optional timestamp recorded for commit.
 
376
        :param timezone: Optional timezone for timestamp.
 
377
        :param committer: Optional committer to set for commit.
 
378
        :param revprops: Optional dictionary of revision properties.
 
379
        :param revision_id: Optional revision id.
 
380
        """
 
381
        return _CommitBuilder(self, parents, config, timestamp, timezone,
 
382
                              committer, revprops, revision_id)
 
383
 
 
384
    def unlock(self):
 
385
        self.control_files.unlock()
 
386
 
 
387
    @needs_read_lock
 
388
    def clone(self, a_bzrdir, revision_id=None, basis=None):
 
389
        """Clone this repository into a_bzrdir using the current format.
 
390
 
 
391
        Currently no check is made that the format of this repository and
 
392
        the bzrdir format are compatible. FIXME RBC 20060201.
 
393
        """
 
394
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
 
395
            # use target default format.
 
396
            result = a_bzrdir.create_repository()
 
397
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
 
398
        elif isinstance(a_bzrdir._format,
 
399
                      (bzrdir.BzrDirFormat4,
 
400
                       bzrdir.BzrDirFormat5,
 
401
                       bzrdir.BzrDirFormat6)):
 
402
            result = a_bzrdir.open_repository()
 
403
        else:
 
404
            result = self._format.initialize(a_bzrdir, shared=self.is_shared())
 
405
        self.copy_content_into(result, revision_id, basis)
 
406
        return result
 
407
 
 
408
    @needs_read_lock
 
409
    def has_revision(self, revision_id):
 
410
        """True if this repository has a copy of the revision."""
 
411
        return self._revision_store.has_revision_id(revision_id,
 
412
                                                    self.get_transaction())
 
413
 
 
414
    @needs_read_lock
 
415
    def get_revision_reconcile(self, revision_id):
 
416
        """'reconcile' helper routine that allows access to a revision always.
 
417
        
 
418
        This variant of get_revision does not cross check the weave graph
 
419
        against the revision one as get_revision does: but it should only
 
420
        be used by reconcile, or reconcile-alike commands that are correcting
 
421
        or testing the revision graph.
 
422
        """
 
423
        if not revision_id or not isinstance(revision_id, basestring):
 
424
            raise errors.InvalidRevisionId(revision_id=revision_id,
 
425
                                           branch=self)
 
426
        return self._revision_store.get_revisions([revision_id],
 
427
                                                  self.get_transaction())[0]
 
428
    @needs_read_lock
 
429
    def get_revisions(self, revision_ids):
 
430
        return self._revision_store.get_revisions(revision_ids,
 
431
                                                  self.get_transaction())
 
432
 
 
433
    @needs_read_lock
 
434
    def get_revision_xml(self, revision_id):
 
435
        rev = self.get_revision(revision_id) 
 
436
        rev_tmp = StringIO()
 
437
        # the current serializer..
 
438
        self._revision_store._serializer.write_revision(rev, rev_tmp)
 
439
        rev_tmp.seek(0)
 
440
        return rev_tmp.getvalue()
 
441
 
 
442
    @needs_read_lock
 
443
    def get_revision(self, revision_id):
 
444
        """Return the Revision object for a named revision"""
 
445
        r = self.get_revision_reconcile(revision_id)
 
446
        # weave corruption can lead to absent revision markers that should be
 
447
        # present.
 
448
        # the following test is reasonably cheap (it needs a single weave read)
 
449
        # and the weave is cached in read transactions. In write transactions
 
450
        # it is not cached but typically we only read a small number of
 
451
        # revisions. For knits when they are introduced we will probably want
 
452
        # to ensure that caching write transactions are in use.
 
453
        inv = self.get_inventory_weave()
 
454
        self._check_revision_parents(r, inv)
 
455
        return r
 
456
 
 
457
    @needs_read_lock
 
458
    def get_deltas_for_revisions(self, revisions):
 
459
        """Produce a generator of revision deltas.
 
460
        
 
461
        Note that the input is a sequence of REVISIONS, not revision_ids.
 
462
        Trees will be held in memory until the generator exits.
 
463
        Each delta is relative to the revision's lefthand predecessor.
 
464
        """
 
465
        required_trees = set()
 
466
        for revision in revisions:
 
467
            required_trees.add(revision.revision_id)
 
468
            required_trees.update(revision.parent_ids[:1])
 
469
        trees = dict((t.get_revision_id(), t) for 
 
470
                     t in self.revision_trees(required_trees))
 
471
        for revision in revisions:
 
472
            if not revision.parent_ids:
 
473
                old_tree = self.revision_tree(None)
 
474
            else:
 
475
                old_tree = trees[revision.parent_ids[0]]
 
476
            yield trees[revision.revision_id].changes_from(old_tree)
 
477
 
 
478
    @needs_read_lock
 
479
    def get_revision_delta(self, revision_id):
 
480
        """Return the delta for one revision.
 
481
 
 
482
        The delta is relative to the left-hand predecessor of the
 
483
        revision.
 
484
        """
 
485
        r = self.get_revision(revision_id)
 
486
        return list(self.get_deltas_for_revisions([r]))[0]
 
487
 
 
488
    def _check_revision_parents(self, revision, inventory):
 
489
        """Private to Repository and Fetch.
 
490
        
 
491
        This checks the parentage of revision in an inventory weave for 
 
492
        consistency and is only applicable to inventory-weave-for-ancestry
 
493
        using repository formats & fetchers.
 
494
        """
 
495
        weave_parents = inventory.get_parents(revision.revision_id)
 
496
        weave_names = inventory.versions()
 
497
        for parent_id in revision.parent_ids:
 
498
            if parent_id in weave_names:
 
499
                # this parent must not be a ghost.
 
500
                if not parent_id in weave_parents:
 
501
                    # but it is a ghost
 
502
                    raise errors.CorruptRepository(self)
 
503
 
 
504
    @needs_write_lock
 
505
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
506
        signature = gpg_strategy.sign(plaintext)
 
507
        self._revision_store.add_revision_signature_text(revision_id,
 
508
                                                         signature,
 
509
                                                         self.get_transaction())
 
510
 
 
511
    def fileids_altered_by_revision_ids(self, revision_ids):
 
512
        """Find the file ids and versions affected by revisions.
 
513
 
 
514
        :param revisions: an iterable containing revision ids.
 
515
        :return: a dictionary mapping altered file-ids to an iterable of
 
516
        revision_ids. Each altered file-ids has the exact revision_ids that
 
517
        altered it listed explicitly.
 
518
        """
 
519
        assert self._serializer.support_altered_by_hack, \
 
520
            ("fileids_altered_by_revision_ids only supported for branches " 
 
521
             "which store inventory as unnested xml, not on %r" % self)
 
522
        selected_revision_ids = set(revision_ids)
 
523
        w = self.get_inventory_weave()
 
524
        result = {}
 
525
 
 
526
        # this code needs to read every new line in every inventory for the
 
527
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
 
528
        # not present in one of those inventories is unnecessary but not 
 
529
        # harmful because we are filtering by the revision id marker in the
 
530
        # inventory lines : we only select file ids altered in one of those  
 
531
        # revisions. We don't need to see all lines in the inventory because
 
532
        # only those added in an inventory in rev X can contain a revision=X
 
533
        # line.
 
534
        unescape_revid_cache = {}
 
535
        unescape_fileid_cache = {}
 
536
 
 
537
        # jam 20061218 In a big fetch, this handles hundreds of thousands
 
538
        # of lines, so it has had a lot of inlining and optimizing done.
 
539
        # Sorry that it is a little bit messy.
 
540
        # Move several functions to be local variables, since this is a long
 
541
        # running loop.
 
542
        search = self._file_ids_altered_regex.search
 
543
        unescape = _unescape_xml
 
544
        setdefault = result.setdefault
 
545
        pb = ui.ui_factory.nested_progress_bar()
 
546
        try:
 
547
            for line in w.iter_lines_added_or_present_in_versions(
 
548
                                        selected_revision_ids, pb=pb):
 
549
                match = search(line)
 
550
                if match is None:
 
551
                    continue
 
552
                # One call to match.group() returning multiple items is quite a
 
553
                # bit faster than 2 calls to match.group() each returning 1
 
554
                file_id, revision_id = match.group('file_id', 'revision_id')
 
555
 
 
556
                # Inlining the cache lookups helps a lot when you make 170,000
 
557
                # lines and 350k ids, versus 8.4 unique ids.
 
558
                # Using a cache helps in 2 ways:
 
559
                #   1) Avoids unnecessary decoding calls
 
560
                #   2) Re-uses cached strings, which helps in future set and
 
561
                #      equality checks.
 
562
                # (2) is enough that removing encoding entirely along with
 
563
                # the cache (so we are using plain strings) results in no
 
564
                # performance improvement.
 
565
                try:
 
566
                    revision_id = unescape_revid_cache[revision_id]
 
567
                except KeyError:
 
568
                    unescaped = unescape(revision_id)
 
569
                    unescape_revid_cache[revision_id] = unescaped
 
570
                    revision_id = unescaped
 
571
 
 
572
                if revision_id in selected_revision_ids:
 
573
                    try:
 
574
                        file_id = unescape_fileid_cache[file_id]
 
575
                    except KeyError:
 
576
                        unescaped = unescape(file_id)
 
577
                        unescape_fileid_cache[file_id] = unescaped
 
578
                        file_id = unescaped
 
579
                    setdefault(file_id, set()).add(revision_id)
 
580
        finally:
 
581
            pb.finished()
 
582
        return result
 
583
 
 
584
    @needs_read_lock
 
585
    def get_inventory_weave(self):
 
586
        return self.control_weaves.get_weave('inventory',
 
587
            self.get_transaction())
 
588
 
 
589
    @needs_read_lock
 
590
    def get_inventory(self, revision_id):
 
591
        """Get Inventory object by hash."""
 
592
        return self.deserialise_inventory(
 
593
            revision_id, self.get_inventory_xml(revision_id))
 
594
 
 
595
    def deserialise_inventory(self, revision_id, xml):
 
596
        """Transform the xml into an inventory object. 
 
597
 
 
598
        :param revision_id: The expected revision id of the inventory.
 
599
        :param xml: A serialised inventory.
 
600
        """
 
601
        result = self._serializer.read_inventory_from_string(xml)
 
602
        result.root.revision = revision_id
 
603
        return result
 
604
 
 
605
    def serialise_inventory(self, inv):
 
606
        return self._serializer.write_inventory_to_string(inv)
 
607
 
 
608
    @needs_read_lock
 
609
    def get_inventory_xml(self, revision_id):
 
610
        """Get inventory XML as a file object."""
 
611
        try:
 
612
            assert isinstance(revision_id, basestring), type(revision_id)
 
613
            iw = self.get_inventory_weave()
 
614
            return iw.get_text(revision_id)
 
615
        except IndexError:
 
616
            raise errors.HistoryMissing(self, 'inventory', revision_id)
 
617
 
 
618
    @needs_read_lock
 
619
    def get_inventory_sha1(self, revision_id):
 
620
        """Return the sha1 hash of the inventory entry
 
621
        """
 
622
        return self.get_revision(revision_id).inventory_sha1
 
623
 
 
624
    @needs_read_lock
 
625
    def get_revision_graph(self, revision_id=None):
 
626
        """Return a dictionary containing the revision graph.
 
627
        
 
628
        :param revision_id: The revision_id to get a graph from. If None, then
 
629
        the entire revision graph is returned. This is a deprecated mode of
 
630
        operation and will be removed in the future.
 
631
        :return: a dictionary of revision_id->revision_parents_list.
 
632
        """
 
633
        # special case NULL_REVISION
 
634
        if revision_id == _mod_revision.NULL_REVISION:
 
635
            return {}
 
636
        a_weave = self.get_inventory_weave()
 
637
        all_revisions = self._eliminate_revisions_not_present(
 
638
                                a_weave.versions())
 
639
        entire_graph = dict([(node, a_weave.get_parents(node)) for 
 
640
                             node in all_revisions])
 
641
        if revision_id is None:
 
642
            return entire_graph
 
643
        elif revision_id not in entire_graph:
 
644
            raise errors.NoSuchRevision(self, revision_id)
 
645
        else:
 
646
            # add what can be reached from revision_id
 
647
            result = {}
 
648
            pending = set([revision_id])
 
649
            while len(pending) > 0:
 
650
                node = pending.pop()
 
651
                result[node] = entire_graph[node]
 
652
                for revision_id in result[node]:
 
653
                    if revision_id not in result:
 
654
                        pending.add(revision_id)
 
655
            return result
 
656
 
 
657
    @needs_read_lock
 
658
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
659
        """Return a graph of the revisions with ghosts marked as applicable.
 
660
 
 
661
        :param revision_ids: an iterable of revisions to graph or None for all.
 
662
        :return: a Graph object with the graph reachable from revision_ids.
 
663
        """
 
664
        result = graph.Graph()
 
665
        if not revision_ids:
 
666
            pending = set(self.all_revision_ids())
 
667
            required = set([])
 
668
        else:
 
669
            pending = set(revision_ids)
 
670
            # special case NULL_REVISION
 
671
            if _mod_revision.NULL_REVISION in pending:
 
672
                pending.remove(_mod_revision.NULL_REVISION)
 
673
            required = set(pending)
 
674
        done = set([])
 
675
        while len(pending):
 
676
            revision_id = pending.pop()
 
677
            try:
 
678
                rev = self.get_revision(revision_id)
 
679
            except errors.NoSuchRevision:
 
680
                if revision_id in required:
 
681
                    raise
 
682
                # a ghost
 
683
                result.add_ghost(revision_id)
 
684
                continue
 
685
            for parent_id in rev.parent_ids:
 
686
                # is this queued or done ?
 
687
                if (parent_id not in pending and
 
688
                    parent_id not in done):
 
689
                    # no, queue it.
 
690
                    pending.add(parent_id)
 
691
            result.add_node(revision_id, rev.parent_ids)
 
692
            done.add(revision_id)
 
693
        return result
 
694
 
 
695
    @needs_read_lock
 
696
    def get_revision_inventory(self, revision_id):
 
697
        """Return inventory of a past revision."""
 
698
        # TODO: Unify this with get_inventory()
 
699
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
700
        # must be the same as its revision, so this is trivial.
 
701
        if revision_id is None:
 
702
            # This does not make sense: if there is no revision,
 
703
            # then it is the current tree inventory surely ?!
 
704
            # and thus get_root_id() is something that looks at the last
 
705
            # commit on the branch, and the get_root_id is an inventory check.
 
706
            raise NotImplementedError
 
707
            # return Inventory(self.get_root_id())
 
708
        else:
 
709
            return self.get_inventory(revision_id)
 
710
 
 
711
    @needs_read_lock
 
712
    def is_shared(self):
 
713
        """Return True if this repository is flagged as a shared repository."""
 
714
        raise NotImplementedError(self.is_shared)
 
715
 
 
716
    @needs_write_lock
 
717
    def reconcile(self, other=None, thorough=False):
 
718
        """Reconcile this repository."""
 
719
        from bzrlib.reconcile import RepoReconciler
 
720
        reconciler = RepoReconciler(self, thorough=thorough)
 
721
        reconciler.reconcile()
 
722
        return reconciler
 
723
    
 
724
    @needs_read_lock
 
725
    def revision_tree(self, revision_id):
 
726
        """Return Tree for a revision on this branch.
 
727
 
 
728
        `revision_id` may be None for the empty tree revision.
 
729
        """
 
730
        # TODO: refactor this to use an existing revision object
 
731
        # so we don't need to read it in twice.
 
732
        if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
 
733
            return RevisionTree(self, Inventory(root_id=None), 
 
734
                                _mod_revision.NULL_REVISION)
 
735
        else:
 
736
            inv = self.get_revision_inventory(revision_id)
 
737
            return RevisionTree(self, inv, revision_id)
 
738
 
 
739
    @needs_read_lock
 
740
    def revision_trees(self, revision_ids):
 
741
        """Return Tree for a revision on this branch.
 
742
 
 
743
        `revision_id` may not be None or 'null:'"""
 
744
        assert None not in revision_ids
 
745
        assert _mod_revision.NULL_REVISION not in revision_ids
 
746
        texts = self.get_inventory_weave().get_texts(revision_ids)
 
747
        for text, revision_id in zip(texts, revision_ids):
 
748
            inv = self.deserialise_inventory(revision_id, text)
 
749
            yield RevisionTree(self, inv, revision_id)
 
750
 
 
751
    @needs_read_lock
 
752
    def get_ancestry(self, revision_id):
 
753
        """Return a list of revision-ids integrated by a revision.
 
754
 
 
755
        The first element of the list is always None, indicating the origin 
 
756
        revision.  This might change when we have history horizons, or 
 
757
        perhaps we should have a new API.
 
758
        
 
759
        This is topologically sorted.
 
760
        """
 
761
        if revision_id is None:
 
762
            return [None]
 
763
        if not self.has_revision(revision_id):
 
764
            raise errors.NoSuchRevision(self, revision_id)
 
765
        w = self.get_inventory_weave()
 
766
        candidates = w.get_ancestry(revision_id)
 
767
        return [None] + candidates # self._eliminate_revisions_not_present(candidates)
 
768
 
 
769
    @needs_read_lock
 
770
    def print_file(self, file, revision_id):
 
771
        """Print `file` to stdout.
 
772
        
 
773
        FIXME RBC 20060125 as John Meinel points out this is a bad api
 
774
        - it writes to stdout, it assumes that that is valid etc. Fix
 
775
        by creating a new more flexible convenience function.
 
776
        """
 
777
        tree = self.revision_tree(revision_id)
 
778
        # use inventory as it was in that revision
 
779
        file_id = tree.inventory.path2id(file)
 
780
        if not file_id:
 
781
            # TODO: jam 20060427 Write a test for this code path
 
782
            #       it had a bug in it, and was raising the wrong
 
783
            #       exception.
 
784
            raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
 
785
        tree.print_file(file_id)
 
786
 
 
787
    def get_transaction(self):
 
788
        return self.control_files.get_transaction()
 
789
 
 
790
    def revision_parents(self, revid):
 
791
        return self.get_inventory_weave().parent_names(revid)
 
792
 
 
793
    @needs_write_lock
 
794
    def set_make_working_trees(self, new_value):
 
795
        """Set the policy flag for making working trees when creating branches.
 
796
 
 
797
        This only applies to branches that use this repository.
 
798
 
 
799
        The default is 'True'.
 
800
        :param new_value: True to restore the default, False to disable making
 
801
                          working trees.
 
802
        """
 
803
        raise NotImplementedError(self.set_make_working_trees)
 
804
    
 
805
    def make_working_trees(self):
 
806
        """Returns the policy for making working trees on new branches."""
 
807
        raise NotImplementedError(self.make_working_trees)
 
808
 
 
809
    @needs_write_lock
 
810
    def sign_revision(self, revision_id, gpg_strategy):
 
811
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
812
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
813
 
 
814
    @needs_read_lock
 
815
    def has_signature_for_revision_id(self, revision_id):
 
816
        """Query for a revision signature for revision_id in the repository."""
 
817
        return self._revision_store.has_signature(revision_id,
 
818
                                                  self.get_transaction())
 
819
 
 
820
    @needs_read_lock
 
821
    def get_signature_text(self, revision_id):
 
822
        """Return the text for a signature."""
 
823
        return self._revision_store.get_signature_text(revision_id,
 
824
                                                       self.get_transaction())
 
825
 
 
826
    @needs_read_lock
 
827
    def check(self, revision_ids):
 
828
        """Check consistency of all history of given revision_ids.
 
829
 
 
830
        Different repository implementations should override _check().
 
831
 
 
832
        :param revision_ids: A non-empty list of revision_ids whose ancestry
 
833
             will be checked.  Typically the last revision_id of a branch.
 
834
        """
 
835
        if not revision_ids:
 
836
            raise ValueError("revision_ids must be non-empty in %s.check" 
 
837
                    % (self,))
 
838
        return self._check(revision_ids)
 
839
 
 
840
    def _check(self, revision_ids):
 
841
        result = check.Check(self)
 
842
        result.check()
 
843
        return result
 
844
 
 
845
    def _warn_if_deprecated(self):
 
846
        global _deprecation_warning_done
 
847
        if _deprecation_warning_done:
 
848
            return
 
849
        _deprecation_warning_done = True
 
850
        warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
 
851
                % (self._format, self.bzrdir.transport.base))
 
852
 
 
853
    def supports_rich_root(self):
 
854
        return self._format.rich_root_data
 
855
 
 
856
    def _check_ascii_revisionid(self, revision_id, method):
 
857
        """Private helper for ascii-only repositories."""
 
858
        # weave repositories refuse to store revisionids that are non-ascii.
 
859
        if revision_id is not None:
 
860
            # weaves require ascii revision ids.
 
861
            if isinstance(revision_id, unicode):
 
862
                try:
 
863
                    revision_id.encode('ascii')
 
864
                except UnicodeEncodeError:
 
865
                    raise errors.NonAsciiRevisionId(method, self)
 
866
 
 
867
    def set_tag(self, tag_name, tag_target):
 
868
        self._tag_store.set_tag(tag_name, tag_target)
 
869
 
 
870
    def lookup_tag(self, tag_name):
 
871
        return self._tag_store.lookup_tag(tag_name)
 
872
 
 
873
    def get_tag_dict(self):
 
874
        return self._tag_store.get_tag_dict()
 
875
 
 
876
 
 
877
class AllInOneRepository(Repository):
 
878
    """Legacy support - the repository behaviour for all-in-one branches."""
 
879
 
 
880
    def __init__(self, _format, a_bzrdir, _revision_store, control_store, text_store):
 
881
        # we reuse one control files instance.
 
882
        dir_mode = a_bzrdir._control_files._dir_mode
 
883
        file_mode = a_bzrdir._control_files._file_mode
 
884
 
 
885
        def get_store(name, compressed=True, prefixed=False):
 
886
            # FIXME: This approach of assuming stores are all entirely compressed
 
887
            # or entirely uncompressed is tidy, but breaks upgrade from 
 
888
            # some existing branches where there's a mixture; we probably 
 
889
            # still want the option to look for both.
 
890
            relpath = a_bzrdir._control_files._escape(name)
 
891
            store = TextStore(a_bzrdir._control_files._transport.clone(relpath),
 
892
                              prefixed=prefixed, compressed=compressed,
 
893
                              dir_mode=dir_mode,
 
894
                              file_mode=file_mode)
 
895
            #if self._transport.should_cache():
 
896
            #    cache_path = os.path.join(self.cache_root, name)
 
897
            #    os.mkdir(cache_path)
 
898
            #    store = bzrlib.store.CachedStore(store, cache_path)
 
899
            return store
 
900
 
 
901
        # not broken out yet because the controlweaves|inventory_store
 
902
        # and text_store | weave_store bits are still different.
 
903
        if isinstance(_format, RepositoryFormat4):
 
904
            # cannot remove these - there is still no consistent api 
 
905
            # which allows access to this old info.
 
906
            self.inventory_store = get_store('inventory-store')
 
907
            text_store = get_store('text-store')
 
908
        super(AllInOneRepository, self).__init__(_format, a_bzrdir, a_bzrdir._control_files, _revision_store, control_store, text_store)
 
909
 
 
910
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
911
                           timezone=None, committer=None, revprops=None,
 
912
                           revision_id=None):
 
913
        self._check_ascii_revisionid(revision_id, self.get_commit_builder)
 
914
        return Repository.get_commit_builder(self, branch, parents, config,
 
915
            timestamp, timezone, committer, revprops, revision_id)
 
916
 
 
917
    @needs_read_lock
 
918
    def is_shared(self):
 
919
        """AllInOne repositories cannot be shared."""
 
920
        return False
 
921
 
 
922
    @needs_write_lock
 
923
    def set_make_working_trees(self, new_value):
 
924
        """Set the policy flag for making working trees when creating branches.
 
925
 
 
926
        This only applies to branches that use this repository.
 
927
 
 
928
        The default is 'True'.
 
929
        :param new_value: True to restore the default, False to disable making
 
930
                          working trees.
 
931
        """
 
932
        raise NotImplementedError(self.set_make_working_trees)
 
933
    
 
934
    def make_working_trees(self):
 
935
        """Returns the policy for making working trees on new branches."""
 
936
        return True
 
937
 
 
938
 
 
939
def install_revision(repository, rev, revision_tree):
 
940
    """Install all revision data into a repository."""
 
941
    present_parents = []
 
942
    parent_trees = {}
 
943
    for p_id in rev.parent_ids:
 
944
        if repository.has_revision(p_id):
 
945
            present_parents.append(p_id)
 
946
            parent_trees[p_id] = repository.revision_tree(p_id)
 
947
        else:
 
948
            parent_trees[p_id] = repository.revision_tree(None)
 
949
 
 
950
    inv = revision_tree.inventory
 
951
    entries = inv.iter_entries()
 
952
    # backwards compatability hack: skip the root id.
 
953
    if not repository.supports_rich_root():
 
954
        path, root = entries.next()
 
955
        if root.revision != rev.revision_id:
 
956
            raise errors.IncompatibleRevision(repr(repository))
 
957
    # Add the texts that are not already present
 
958
    for path, ie in entries:
 
959
        w = repository.weave_store.get_weave_or_empty(ie.file_id,
 
960
                repository.get_transaction())
 
961
        if ie.revision not in w:
 
962
            text_parents = []
 
963
            # FIXME: TODO: The following loop *may* be overlapping/duplicate
 
964
            # with InventoryEntry.find_previous_heads(). if it is, then there
 
965
            # is a latent bug here where the parents may have ancestors of each
 
966
            # other. RBC, AB
 
967
            for revision, tree in parent_trees.iteritems():
 
968
                if ie.file_id not in tree:
 
969
                    continue
 
970
                parent_id = tree.inventory[ie.file_id].revision
 
971
                if parent_id in text_parents:
 
972
                    continue
 
973
                text_parents.append(parent_id)
 
974
                    
 
975
            vfile = repository.weave_store.get_weave_or_empty(ie.file_id, 
 
976
                repository.get_transaction())
 
977
            lines = revision_tree.get_file(ie.file_id).readlines()
 
978
            vfile.add_lines(rev.revision_id, text_parents, lines)
 
979
    try:
 
980
        # install the inventory
 
981
        repository.add_inventory(rev.revision_id, inv, present_parents)
 
982
    except errors.RevisionAlreadyPresent:
 
983
        pass
 
984
    repository.add_revision(rev.revision_id, rev, inv)
 
985
 
 
986
 
 
987
class MetaDirRepository(Repository):
 
988
    """Repositories in the new meta-dir layout."""
 
989
 
 
990
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
991
        super(MetaDirRepository, self).__init__(_format,
 
992
                                                a_bzrdir,
 
993
                                                control_files,
 
994
                                                _revision_store,
 
995
                                                control_store,
 
996
                                                text_store)
 
997
        dir_mode = self.control_files._dir_mode
 
998
        file_mode = self.control_files._file_mode
 
999
 
 
1000
    @needs_read_lock
 
1001
    def is_shared(self):
 
1002
        """Return True if this repository is flagged as a shared repository."""
 
1003
        return self.control_files._transport.has('shared-storage')
 
1004
 
 
1005
    @needs_write_lock
 
1006
    def set_make_working_trees(self, new_value):
 
1007
        """Set the policy flag for making working trees when creating branches.
 
1008
 
 
1009
        This only applies to branches that use this repository.
 
1010
 
 
1011
        The default is 'True'.
 
1012
        :param new_value: True to restore the default, False to disable making
 
1013
                          working trees.
 
1014
        """
 
1015
        if new_value:
 
1016
            try:
 
1017
                self.control_files._transport.delete('no-working-trees')
 
1018
            except errors.NoSuchFile:
 
1019
                pass
 
1020
        else:
 
1021
            self.control_files.put_utf8('no-working-trees', '')
 
1022
    
 
1023
    def make_working_trees(self):
 
1024
        """Returns the policy for making working trees on new branches."""
 
1025
        return not self.control_files._transport.has('no-working-trees')
 
1026
 
 
1027
 
 
1028
class WeaveMetaDirRepository(MetaDirRepository):
 
1029
    """A subclass of MetaDirRepository to set weave specific policy."""
 
1030
 
 
1031
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
1032
                           timezone=None, committer=None, revprops=None,
 
1033
                           revision_id=None):
 
1034
        self._check_ascii_revisionid(revision_id, self.get_commit_builder)
 
1035
        return MetaDirRepository.get_commit_builder(self, branch, parents,
 
1036
            config, timestamp, timezone, committer, revprops, revision_id)
 
1037
 
 
1038
 
 
1039
class KnitRepository(MetaDirRepository):
 
1040
    """Knit format repository."""
 
1041
 
 
1042
    def _warn_if_deprecated(self):
 
1043
        # This class isn't deprecated
 
1044
        pass
 
1045
 
 
1046
    def _inventory_add_lines(self, inv_vf, revid, parents, lines):
 
1047
        inv_vf.add_lines_with_ghosts(revid, parents, lines)
 
1048
 
 
1049
    @needs_read_lock
 
1050
    def _all_revision_ids(self):
 
1051
        """See Repository.all_revision_ids()."""
 
1052
        # Knits get the revision graph from the index of the revision knit, so
 
1053
        # it's always possible even if they're on an unlistable transport.
 
1054
        return self._revision_store.all_revision_ids(self.get_transaction())
 
1055
 
 
1056
    def fileid_involved_between_revs(self, from_revid, to_revid):
 
1057
        """Find file_id(s) which are involved in the changes between revisions.
 
1058
 
 
1059
        This determines the set of revisions which are involved, and then
 
1060
        finds all file ids affected by those revisions.
 
1061
        """
 
1062
        vf = self._get_revision_vf()
 
1063
        from_set = set(vf.get_ancestry(from_revid))
 
1064
        to_set = set(vf.get_ancestry(to_revid))
 
1065
        changed = to_set.difference(from_set)
 
1066
        return self._fileid_involved_by_set(changed)
 
1067
 
 
1068
    def fileid_involved(self, last_revid=None):
 
1069
        """Find all file_ids modified in the ancestry of last_revid.
 
1070
 
 
1071
        :param last_revid: If None, last_revision() will be used.
 
1072
        """
 
1073
        if not last_revid:
 
1074
            changed = set(self.all_revision_ids())
 
1075
        else:
 
1076
            changed = set(self.get_ancestry(last_revid))
 
1077
        if None in changed:
 
1078
            changed.remove(None)
 
1079
        return self._fileid_involved_by_set(changed)
 
1080
 
 
1081
    @needs_read_lock
 
1082
    def get_ancestry(self, revision_id):
 
1083
        """Return a list of revision-ids integrated by a revision.
 
1084
        
 
1085
        This is topologically sorted.
 
1086
        """
 
1087
        if revision_id is None:
 
1088
            return [None]
 
1089
        vf = self._get_revision_vf()
 
1090
        try:
 
1091
            return [None] + vf.get_ancestry(revision_id)
 
1092
        except errors.RevisionNotPresent:
 
1093
            raise errors.NoSuchRevision(self, revision_id)
 
1094
 
 
1095
    @needs_read_lock
 
1096
    def get_revision(self, revision_id):
 
1097
        """Return the Revision object for a named revision"""
 
1098
        return self.get_revision_reconcile(revision_id)
 
1099
 
 
1100
    @needs_read_lock
 
1101
    def get_revision_graph(self, revision_id=None):
 
1102
        """Return a dictionary containing the revision graph.
 
1103
 
 
1104
        :param revision_id: The revision_id to get a graph from. If None, then
 
1105
        the entire revision graph is returned. This is a deprecated mode of
 
1106
        operation and will be removed in the future.
 
1107
        :return: a dictionary of revision_id->revision_parents_list.
 
1108
        """
 
1109
        # special case NULL_REVISION
 
1110
        if revision_id == _mod_revision.NULL_REVISION:
 
1111
            return {}
 
1112
        a_weave = self._get_revision_vf()
 
1113
        entire_graph = a_weave.get_graph()
 
1114
        if revision_id is None:
 
1115
            return a_weave.get_graph()
 
1116
        elif revision_id not in a_weave:
 
1117
            raise errors.NoSuchRevision(self, revision_id)
 
1118
        else:
 
1119
            # add what can be reached from revision_id
 
1120
            result = {}
 
1121
            pending = set([revision_id])
 
1122
            while len(pending) > 0:
 
1123
                node = pending.pop()
 
1124
                result[node] = a_weave.get_parents(node)
 
1125
                for revision_id in result[node]:
 
1126
                    if revision_id not in result:
 
1127
                        pending.add(revision_id)
 
1128
            return result
 
1129
 
 
1130
    @needs_read_lock
 
1131
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
1132
        """Return a graph of the revisions with ghosts marked as applicable.
 
1133
 
 
1134
        :param revision_ids: an iterable of revisions to graph or None for all.
 
1135
        :return: a Graph object with the graph reachable from revision_ids.
 
1136
        """
 
1137
        result = graph.Graph()
 
1138
        vf = self._get_revision_vf()
 
1139
        versions = set(vf.versions())
 
1140
        if not revision_ids:
 
1141
            pending = set(self.all_revision_ids())
 
1142
            required = set([])
 
1143
        else:
 
1144
            pending = set(revision_ids)
 
1145
            # special case NULL_REVISION
 
1146
            if _mod_revision.NULL_REVISION in pending:
 
1147
                pending.remove(_mod_revision.NULL_REVISION)
 
1148
            required = set(pending)
 
1149
        done = set([])
 
1150
        while len(pending):
 
1151
            revision_id = pending.pop()
 
1152
            if not revision_id in versions:
 
1153
                if revision_id in required:
 
1154
                    raise errors.NoSuchRevision(self, revision_id)
 
1155
                # a ghost
 
1156
                result.add_ghost(revision_id)
 
1157
                # mark it as done so we don't try for it again.
 
1158
                done.add(revision_id)
 
1159
                continue
 
1160
            parent_ids = vf.get_parents_with_ghosts(revision_id)
 
1161
            for parent_id in parent_ids:
 
1162
                # is this queued or done ?
 
1163
                if (parent_id not in pending and
 
1164
                    parent_id not in done):
 
1165
                    # no, queue it.
 
1166
                    pending.add(parent_id)
 
1167
            result.add_node(revision_id, parent_ids)
 
1168
            done.add(revision_id)
 
1169
        return result
 
1170
 
 
1171
    def _get_revision_vf(self):
 
1172
        """:return: a versioned file containing the revisions."""
 
1173
        vf = self._revision_store.get_revision_file(self.get_transaction())
 
1174
        return vf
 
1175
 
 
1176
    @needs_write_lock
 
1177
    def reconcile(self, other=None, thorough=False):
 
1178
        """Reconcile this repository."""
 
1179
        from bzrlib.reconcile import KnitReconciler
 
1180
        reconciler = KnitReconciler(self, thorough=thorough)
 
1181
        reconciler.reconcile()
 
1182
        return reconciler
 
1183
    
 
1184
    def revision_parents(self, revision_id):
 
1185
        return self._get_revision_vf().get_parents(revision_id)
 
1186
 
 
1187
 
 
1188
class KnitRepository2(KnitRepository):
 
1189
    """Experimental enhanced knit format"""
 
1190
 
 
1191
    # corresponds to RepositoryFormatKnit2
 
1192
    
 
1193
    # TODO: within a lock scope, we could keep the tags in memory...
 
1194
    
 
1195
    _tag_store_class = _BasicTagStore
 
1196
 
 
1197
    def __init__(self, _format, a_bzrdir, control_files, _revision_store,
 
1198
                 control_store, text_store):
 
1199
        KnitRepository.__init__(self, _format, a_bzrdir, control_files,
 
1200
                              _revision_store, control_store, text_store)
 
1201
        self._transport = control_files._transport
 
1202
        self._serializer = xml6.serializer_v6
 
1203
 
 
1204
    def deserialise_inventory(self, revision_id, xml):
 
1205
        """Transform the xml into an inventory object. 
 
1206
 
 
1207
        :param revision_id: The expected revision id of the inventory.
 
1208
        :param xml: A serialised inventory.
 
1209
        """
 
1210
        result = self._serializer.read_inventory_from_string(xml)
 
1211
        assert result.root.revision is not None
 
1212
        return result
 
1213
 
 
1214
    def serialise_inventory(self, inv):
 
1215
        """Transform the inventory object into XML text.
 
1216
 
 
1217
        :param revision_id: The expected revision id of the inventory.
 
1218
        :param xml: A serialised inventory.
 
1219
        """
 
1220
        assert inv.revision_id is not None
 
1221
        assert inv.root.revision is not None
 
1222
        return KnitRepository.serialise_inventory(self, inv)
 
1223
 
 
1224
    def get_commit_builder(self, branch, parents, config, timestamp=None, 
 
1225
                           timezone=None, committer=None, revprops=None, 
 
1226
                           revision_id=None):
 
1227
        """Obtain a CommitBuilder for this repository.
 
1228
        
 
1229
        :param branch: Branch to commit to.
 
1230
        :param parents: Revision ids of the parents of the new revision.
 
1231
        :param config: Configuration to use.
 
1232
        :param timestamp: Optional timestamp recorded for commit.
 
1233
        :param timezone: Optional timezone for timestamp.
 
1234
        :param committer: Optional committer to set for commit.
 
1235
        :param revprops: Optional dictionary of revision properties.
 
1236
        :param revision_id: Optional revision id.
 
1237
        """
 
1238
        return RootCommitBuilder(self, parents, config, timestamp, timezone,
 
1239
                                 committer, revprops, revision_id)
 
1240
 
 
1241
 
 
1242
#####################################################################
 
1243
# Repository Formats
 
1244
 
 
1245
class RepositoryFormat(object):
 
1246
    """A repository format.
 
1247
 
 
1248
    Formats provide three things:
 
1249
     * An initialization routine to construct repository data on disk.
 
1250
     * a format string which is used when the BzrDir supports versioned
 
1251
       children.
 
1252
     * an open routine which returns a Repository instance.
 
1253
 
 
1254
    Formats are placed in an dict by their format string for reference 
 
1255
    during opening. These should be subclasses of RepositoryFormat
 
1256
    for consistency.
 
1257
 
 
1258
    Once a format is deprecated, just deprecate the initialize and open
 
1259
    methods on the format class. Do not deprecate the object, as the 
 
1260
    object will be created every system load.
 
1261
 
 
1262
    Common instance attributes:
 
1263
    _matchingbzrdir - the bzrdir format that the repository format was
 
1264
    originally written to work with. This can be used if manually
 
1265
    constructing a bzrdir and repository, or more commonly for test suite
 
1266
    parameterisation.
 
1267
    """
 
1268
 
 
1269
    _default_format = None
 
1270
    """The default format used for new repositories."""
 
1271
 
 
1272
    _formats = {}
 
1273
    """The known formats."""
 
1274
 
 
1275
    def __str__(self):
 
1276
        return "<%s>" % self.__class__.__name__
 
1277
 
 
1278
    @classmethod
 
1279
    def find_format(klass, a_bzrdir):
 
1280
        """Return the format for the repository object in a_bzrdir."""
 
1281
        try:
 
1282
            transport = a_bzrdir.get_repository_transport(None)
 
1283
            format_string = transport.get("format").read()
 
1284
            return klass._formats[format_string]
 
1285
        except errors.NoSuchFile:
 
1286
            raise errors.NoRepositoryPresent(a_bzrdir)
 
1287
        except KeyError:
 
1288
            raise errors.UnknownFormatError(format=format_string)
 
1289
 
 
1290
    def _get_control_store(self, repo_transport, control_files):
 
1291
        """Return the control store for this repository."""
 
1292
        raise NotImplementedError(self._get_control_store)
 
1293
    
 
1294
    @classmethod
 
1295
    def get_default_format(klass):
 
1296
        """Return the current default format."""
 
1297
        return klass._default_format
 
1298
 
 
1299
    def get_format_string(self):
 
1300
        """Return the ASCII format string that identifies this format.
 
1301
        
 
1302
        Note that in pre format ?? repositories the format string is 
 
1303
        not permitted nor written to disk.
 
1304
        """
 
1305
        raise NotImplementedError(self.get_format_string)
 
1306
 
 
1307
    def get_format_description(self):
 
1308
        """Return the short description for this format."""
 
1309
        raise NotImplementedError(self.get_format_description)
 
1310
 
 
1311
    def _get_revision_store(self, repo_transport, control_files):
 
1312
        """Return the revision store object for this a_bzrdir."""
 
1313
        raise NotImplementedError(self._get_revision_store)
 
1314
 
 
1315
    def _get_text_rev_store(self,
 
1316
                            transport,
 
1317
                            control_files,
 
1318
                            name,
 
1319
                            compressed=True,
 
1320
                            prefixed=False,
 
1321
                            serializer=None):
 
1322
        """Common logic for getting a revision store for a repository.
 
1323
        
 
1324
        see self._get_revision_store for the subclass-overridable method to 
 
1325
        get the store for a repository.
 
1326
        """
 
1327
        from bzrlib.store.revision.text import TextRevisionStore
 
1328
        dir_mode = control_files._dir_mode
 
1329
        file_mode = control_files._file_mode
 
1330
        text_store = TextStore(transport.clone(name),
 
1331
                              prefixed=prefixed,
 
1332
                              compressed=compressed,
 
1333
                              dir_mode=dir_mode,
 
1334
                              file_mode=file_mode)
 
1335
        _revision_store = TextRevisionStore(text_store, serializer)
 
1336
        return _revision_store
 
1337
 
 
1338
    def _get_versioned_file_store(self,
 
1339
                                  name,
 
1340
                                  transport,
 
1341
                                  control_files,
 
1342
                                  prefixed=True,
 
1343
                                  versionedfile_class=weave.WeaveFile,
 
1344
                                  versionedfile_kwargs={},
 
1345
                                  escaped=False):
 
1346
        weave_transport = control_files._transport.clone(name)
 
1347
        dir_mode = control_files._dir_mode
 
1348
        file_mode = control_files._file_mode
 
1349
        return VersionedFileStore(weave_transport, prefixed=prefixed,
 
1350
                                  dir_mode=dir_mode,
 
1351
                                  file_mode=file_mode,
 
1352
                                  versionedfile_class=versionedfile_class,
 
1353
                                  versionedfile_kwargs=versionedfile_kwargs,
 
1354
                                  escaped=escaped)
 
1355
 
 
1356
    def initialize(self, a_bzrdir, shared=False):
 
1357
        """Initialize a repository of this format in a_bzrdir.
 
1358
 
 
1359
        :param a_bzrdir: The bzrdir to put the new repository in it.
 
1360
        :param shared: The repository should be initialized as a sharable one.
 
1361
 
 
1362
        This may raise UninitializableFormat if shared repository are not
 
1363
        compatible the a_bzrdir.
 
1364
        """
 
1365
 
 
1366
    def is_supported(self):
 
1367
        """Is this format supported?
 
1368
 
 
1369
        Supported formats must be initializable and openable.
 
1370
        Unsupported formats may not support initialization or committing or 
 
1371
        some other features depending on the reason for not being supported.
 
1372
        """
 
1373
        return True
 
1374
 
 
1375
    def check_conversion_target(self, target_format):
 
1376
        raise NotImplementedError(self.check_conversion_target)
 
1377
 
 
1378
    def open(self, a_bzrdir, _found=False):
 
1379
        """Return an instance of this format for the bzrdir a_bzrdir.
 
1380
        
 
1381
        _found is a private parameter, do not use it.
 
1382
        """
 
1383
        raise NotImplementedError(self.open)
 
1384
 
 
1385
    @classmethod
 
1386
    def register_format(klass, format):
 
1387
        klass._formats[format.get_format_string()] = format
 
1388
 
 
1389
    @classmethod
 
1390
    def set_default_format(klass, format):
 
1391
        klass._default_format = format
 
1392
 
 
1393
    @classmethod
 
1394
    def unregister_format(klass, format):
 
1395
        assert klass._formats[format.get_format_string()] is format
 
1396
        del klass._formats[format.get_format_string()]
 
1397
 
 
1398
    def supports_tags(self):
 
1399
        """True if this format supports tags stored in the repository"""
 
1400
        return False  # by default
 
1401
 
 
1402
 
 
1403
class PreSplitOutRepositoryFormat(RepositoryFormat):
 
1404
    """Base class for the pre split out repository formats."""
 
1405
 
 
1406
    rich_root_data = False
 
1407
 
 
1408
    def initialize(self, a_bzrdir, shared=False, _internal=False):
 
1409
        """Create a weave repository.
 
1410
        
 
1411
        TODO: when creating split out bzr branch formats, move this to a common
 
1412
        base for Format5, Format6. or something like that.
 
1413
        """
 
1414
        if shared:
 
1415
            raise errors.IncompatibleFormat(self, a_bzrdir._format)
 
1416
 
 
1417
        if not _internal:
 
1418
            # always initialized when the bzrdir is.
 
1419
            return self.open(a_bzrdir, _found=True)
 
1420
        
 
1421
        # Create an empty weave
 
1422
        sio = StringIO()
 
1423
        weavefile.write_weave_v5(weave.Weave(), sio)
 
1424
        empty_weave = sio.getvalue()
 
1425
 
 
1426
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1427
        dirs = ['revision-store', 'weaves']
 
1428
        files = [('inventory.weave', StringIO(empty_weave)),
 
1429
                 ]
 
1430
        
 
1431
        # FIXME: RBC 20060125 don't peek under the covers
 
1432
        # NB: no need to escape relative paths that are url safe.
 
1433
        control_files = lockable_files.LockableFiles(a_bzrdir.transport,
 
1434
                                'branch-lock', lockable_files.TransportLock)
 
1435
        control_files.create_lock()
 
1436
        control_files.lock_write()
 
1437
        control_files._transport.mkdir_multi(dirs,
 
1438
                mode=control_files._dir_mode)
 
1439
        try:
 
1440
            for file, content in files:
 
1441
                control_files.put(file, content)
 
1442
        finally:
 
1443
            control_files.unlock()
 
1444
        return self.open(a_bzrdir, _found=True)
 
1445
 
 
1446
    def _get_control_store(self, repo_transport, control_files):
 
1447
        """Return the control store for this repository."""
 
1448
        return self._get_versioned_file_store('',
 
1449
                                              repo_transport,
 
1450
                                              control_files,
 
1451
                                              prefixed=False)
 
1452
 
 
1453
    def _get_text_store(self, transport, control_files):
 
1454
        """Get a store for file texts for this format."""
 
1455
        raise NotImplementedError(self._get_text_store)
 
1456
 
 
1457
    def open(self, a_bzrdir, _found=False):
 
1458
        """See RepositoryFormat.open()."""
 
1459
        if not _found:
 
1460
            # we are being called directly and must probe.
 
1461
            raise NotImplementedError
 
1462
 
 
1463
        repo_transport = a_bzrdir.get_repository_transport(None)
 
1464
        control_files = a_bzrdir._control_files
 
1465
        text_store = self._get_text_store(repo_transport, control_files)
 
1466
        control_store = self._get_control_store(repo_transport, control_files)
 
1467
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1468
        return AllInOneRepository(_format=self,
 
1469
                                  a_bzrdir=a_bzrdir,
 
1470
                                  _revision_store=_revision_store,
 
1471
                                  control_store=control_store,
 
1472
                                  text_store=text_store)
 
1473
 
 
1474
    def check_conversion_target(self, target_format):
 
1475
        pass
 
1476
 
 
1477
 
 
1478
class RepositoryFormat4(PreSplitOutRepositoryFormat):
 
1479
    """Bzr repository format 4.
 
1480
 
 
1481
    This repository format has:
 
1482
     - flat stores
 
1483
     - TextStores for texts, inventories,revisions.
 
1484
 
 
1485
    This format is deprecated: it indexes texts using a text id which is
 
1486
    removed in format 5; initialization and write support for this format
 
1487
    has been removed.
 
1488
    """
 
1489
 
 
1490
    def __init__(self):
 
1491
        super(RepositoryFormat4, self).__init__()
 
1492
        self._matchingbzrdir = bzrdir.BzrDirFormat4()
 
1493
 
 
1494
    def get_format_description(self):
 
1495
        """See RepositoryFormat.get_format_description()."""
 
1496
        return "Repository format 4"
 
1497
 
 
1498
    def initialize(self, url, shared=False, _internal=False):
 
1499
        """Format 4 branches cannot be created."""
 
1500
        raise errors.UninitializableFormat(self)
 
1501
 
 
1502
    def is_supported(self):
 
1503
        """Format 4 is not supported.
 
1504
 
 
1505
        It is not supported because the model changed from 4 to 5 and the
 
1506
        conversion logic is expensive - so doing it on the fly was not 
 
1507
        feasible.
 
1508
        """
 
1509
        return False
 
1510
 
 
1511
    def _get_control_store(self, repo_transport, control_files):
 
1512
        """Format 4 repositories have no formal control store at this point.
 
1513
        
 
1514
        This will cause any control-file-needing apis to fail - this is desired.
 
1515
        """
 
1516
        return None
 
1517
    
 
1518
    def _get_revision_store(self, repo_transport, control_files):
 
1519
        """See RepositoryFormat._get_revision_store()."""
 
1520
        from bzrlib.xml4 import serializer_v4
 
1521
        return self._get_text_rev_store(repo_transport,
 
1522
                                        control_files,
 
1523
                                        'revision-store',
 
1524
                                        serializer=serializer_v4)
 
1525
 
 
1526
    def _get_text_store(self, transport, control_files):
 
1527
        """See RepositoryFormat._get_text_store()."""
 
1528
 
 
1529
 
 
1530
class RepositoryFormat5(PreSplitOutRepositoryFormat):
 
1531
    """Bzr control format 5.
 
1532
 
 
1533
    This repository format has:
 
1534
     - weaves for file texts and inventory
 
1535
     - flat stores
 
1536
     - TextStores for revisions and signatures.
 
1537
    """
 
1538
 
 
1539
    def __init__(self):
 
1540
        super(RepositoryFormat5, self).__init__()
 
1541
        self._matchingbzrdir = bzrdir.BzrDirFormat5()
 
1542
 
 
1543
    def get_format_description(self):
 
1544
        """See RepositoryFormat.get_format_description()."""
 
1545
        return "Weave repository format 5"
 
1546
 
 
1547
    def _get_revision_store(self, repo_transport, control_files):
 
1548
        """See RepositoryFormat._get_revision_store()."""
 
1549
        """Return the revision store object for this a_bzrdir."""
 
1550
        return self._get_text_rev_store(repo_transport,
 
1551
                                        control_files,
 
1552
                                        'revision-store',
 
1553
                                        compressed=False)
 
1554
 
 
1555
    def _get_text_store(self, transport, control_files):
 
1556
        """See RepositoryFormat._get_text_store()."""
 
1557
        return self._get_versioned_file_store('weaves', transport, control_files, prefixed=False)
 
1558
 
 
1559
 
 
1560
class RepositoryFormat6(PreSplitOutRepositoryFormat):
 
1561
    """Bzr control format 6.
 
1562
 
 
1563
    This repository format has:
 
1564
     - weaves for file texts and inventory
 
1565
     - hash subdirectory based stores.
 
1566
     - TextStores for revisions and signatures.
 
1567
    """
 
1568
 
 
1569
    def __init__(self):
 
1570
        super(RepositoryFormat6, self).__init__()
 
1571
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
 
1572
 
 
1573
    def get_format_description(self):
 
1574
        """See RepositoryFormat.get_format_description()."""
 
1575
        return "Weave repository format 6"
 
1576
 
 
1577
    def _get_revision_store(self, repo_transport, control_files):
 
1578
        """See RepositoryFormat._get_revision_store()."""
 
1579
        return self._get_text_rev_store(repo_transport,
 
1580
                                        control_files,
 
1581
                                        'revision-store',
 
1582
                                        compressed=False,
 
1583
                                        prefixed=True)
 
1584
 
 
1585
    def _get_text_store(self, transport, control_files):
 
1586
        """See RepositoryFormat._get_text_store()."""
 
1587
        return self._get_versioned_file_store('weaves', transport, control_files)
 
1588
 
 
1589
 
 
1590
class MetaDirRepositoryFormat(RepositoryFormat):
 
1591
    """Common base class for the new repositories using the metadir layout."""
 
1592
 
 
1593
    rich_root_data = False
 
1594
 
 
1595
    def __init__(self):
 
1596
        super(MetaDirRepositoryFormat, self).__init__()
 
1597
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1598
 
 
1599
    def _create_control_files(self, a_bzrdir):
 
1600
        """Create the required files and the initial control_files object."""
 
1601
        # FIXME: RBC 20060125 don't peek under the covers
 
1602
        # NB: no need to escape relative paths that are url safe.
 
1603
        repository_transport = a_bzrdir.get_repository_transport(self)
 
1604
        control_files = lockable_files.LockableFiles(repository_transport,
 
1605
                                'lock', lockdir.LockDir)
 
1606
        control_files.create_lock()
 
1607
        return control_files
 
1608
 
 
1609
    def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
 
1610
        """Upload the initial blank content."""
 
1611
        control_files = self._create_control_files(a_bzrdir)
 
1612
        control_files.lock_write()
 
1613
        try:
 
1614
            control_files._transport.mkdir_multi(dirs,
 
1615
                    mode=control_files._dir_mode)
 
1616
            for file, content in files:
 
1617
                control_files.put(file, content)
 
1618
            for file, content in utf8_files:
 
1619
                control_files.put_utf8(file, content)
 
1620
            if shared == True:
 
1621
                control_files.put_utf8('shared-storage', '')
 
1622
        finally:
 
1623
            control_files.unlock()
 
1624
 
 
1625
 
 
1626
class RepositoryFormat7(MetaDirRepositoryFormat):
 
1627
    """Bzr repository 7.
 
1628
 
 
1629
    This repository format has:
 
1630
     - weaves for file texts and inventory
 
1631
     - hash subdirectory based stores.
 
1632
     - TextStores for revisions and signatures.
 
1633
     - a format marker of its own
 
1634
     - an optional 'shared-storage' flag
 
1635
     - an optional 'no-working-trees' flag
 
1636
    """
 
1637
 
 
1638
    def _get_control_store(self, repo_transport, control_files):
 
1639
        """Return the control store for this repository."""
 
1640
        return self._get_versioned_file_store('',
 
1641
                                              repo_transport,
 
1642
                                              control_files,
 
1643
                                              prefixed=False)
 
1644
 
 
1645
    def get_format_string(self):
 
1646
        """See RepositoryFormat.get_format_string()."""
 
1647
        return "Bazaar-NG Repository format 7"
 
1648
 
 
1649
    def get_format_description(self):
 
1650
        """See RepositoryFormat.get_format_description()."""
 
1651
        return "Weave repository format 7"
 
1652
 
 
1653
    def check_conversion_target(self, target_format):
 
1654
        pass
 
1655
 
 
1656
    def _get_revision_store(self, repo_transport, control_files):
 
1657
        """See RepositoryFormat._get_revision_store()."""
 
1658
        return self._get_text_rev_store(repo_transport,
 
1659
                                        control_files,
 
1660
                                        'revision-store',
 
1661
                                        compressed=False,
 
1662
                                        prefixed=True,
 
1663
                                        )
 
1664
 
 
1665
    def _get_text_store(self, transport, control_files):
 
1666
        """See RepositoryFormat._get_text_store()."""
 
1667
        return self._get_versioned_file_store('weaves',
 
1668
                                              transport,
 
1669
                                              control_files)
 
1670
 
 
1671
    def initialize(self, a_bzrdir, shared=False):
 
1672
        """Create a weave repository.
 
1673
 
 
1674
        :param shared: If true the repository will be initialized as a shared
 
1675
                       repository.
 
1676
        """
 
1677
        # Create an empty weave
 
1678
        sio = StringIO()
 
1679
        weavefile.write_weave_v5(weave.Weave(), sio)
 
1680
        empty_weave = sio.getvalue()
 
1681
 
 
1682
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1683
        dirs = ['revision-store', 'weaves']
 
1684
        files = [('inventory.weave', StringIO(empty_weave)), 
 
1685
                 ]
 
1686
        utf8_files = [('format', self.get_format_string())]
 
1687
 
 
1688
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1689
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
1690
 
 
1691
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1692
        """See RepositoryFormat.open().
 
1693
        
 
1694
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1695
                                    repository at a slightly different url
 
1696
                                    than normal. I.e. during 'upgrade'.
 
1697
        """
 
1698
        if not _found:
 
1699
            format = RepositoryFormat.find_format(a_bzrdir)
 
1700
            assert format.__class__ ==  self.__class__
 
1701
        if _override_transport is not None:
 
1702
            repo_transport = _override_transport
 
1703
        else:
 
1704
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1705
        control_files = lockable_files.LockableFiles(repo_transport,
 
1706
                                'lock', lockdir.LockDir)
 
1707
        text_store = self._get_text_store(repo_transport, control_files)
 
1708
        control_store = self._get_control_store(repo_transport, control_files)
 
1709
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1710
        return WeaveMetaDirRepository(_format=self,
 
1711
            a_bzrdir=a_bzrdir,
 
1712
            control_files=control_files,
 
1713
            _revision_store=_revision_store,
 
1714
            control_store=control_store,
 
1715
            text_store=text_store)
 
1716
 
 
1717
 
 
1718
class RepositoryFormatKnit(MetaDirRepositoryFormat):
 
1719
    """Bzr repository knit format (generalized). 
 
1720
 
 
1721
    This repository format has:
 
1722
     - knits for file texts and inventory
 
1723
     - hash subdirectory based stores.
 
1724
     - knits for revisions and signatures
 
1725
     - TextStores for revisions and signatures.
 
1726
     - a format marker of its own
 
1727
     - an optional 'shared-storage' flag
 
1728
     - an optional 'no-working-trees' flag
 
1729
     - a LockDir lock
 
1730
    """
 
1731
 
 
1732
    def _get_control_store(self, repo_transport, control_files):
 
1733
        """Return the control store for this repository."""
 
1734
        return VersionedFileStore(
 
1735
            repo_transport,
 
1736
            prefixed=False,
 
1737
            file_mode=control_files._file_mode,
 
1738
            versionedfile_class=knit.KnitVersionedFile,
 
1739
            versionedfile_kwargs={'factory':knit.KnitPlainFactory()},
 
1740
            )
 
1741
 
 
1742
    def _get_revision_store(self, repo_transport, control_files):
 
1743
        """See RepositoryFormat._get_revision_store()."""
 
1744
        from bzrlib.store.revision.knit import KnitRevisionStore
 
1745
        versioned_file_store = VersionedFileStore(
 
1746
            repo_transport,
 
1747
            file_mode=control_files._file_mode,
 
1748
            prefixed=False,
 
1749
            precious=True,
 
1750
            versionedfile_class=knit.KnitVersionedFile,
 
1751
            versionedfile_kwargs={'delta':False,
 
1752
                                  'factory':knit.KnitPlainFactory(),
 
1753
                                 },
 
1754
            escaped=True,
 
1755
            )
 
1756
        return KnitRevisionStore(versioned_file_store)
 
1757
 
 
1758
    def _get_text_store(self, transport, control_files):
 
1759
        """See RepositoryFormat._get_text_store()."""
 
1760
        return self._get_versioned_file_store('knits',
 
1761
                                  transport,
 
1762
                                  control_files,
 
1763
                                  versionedfile_class=knit.KnitVersionedFile,
 
1764
                                  versionedfile_kwargs={
 
1765
                                      'create_parent_dir':True,
 
1766
                                      'delay_create':True,
 
1767
                                      'dir_mode':control_files._dir_mode,
 
1768
                                  },
 
1769
                                  escaped=True)
 
1770
 
 
1771
    def initialize(self, a_bzrdir, shared=False):
 
1772
        """Create a knit format 1 repository.
 
1773
 
 
1774
        :param a_bzrdir: bzrdir to contain the new repository; must already
 
1775
            be initialized.
 
1776
        :param shared: If true the repository will be initialized as a shared
 
1777
                       repository.
 
1778
        """
 
1779
        mutter('creating repository in %s.', a_bzrdir.transport.base)
 
1780
        dirs = ['revision-store', 'knits']
 
1781
        files = []
 
1782
        utf8_files = [('format', self.get_format_string())]
 
1783
        
 
1784
        self._upload_blank_content(a_bzrdir, dirs, files, utf8_files, shared)
 
1785
        repo_transport = a_bzrdir.get_repository_transport(None)
 
1786
        control_files = lockable_files.LockableFiles(repo_transport,
 
1787
                                'lock', lockdir.LockDir)
 
1788
        control_store = self._get_control_store(repo_transport, control_files)
 
1789
        transaction = transactions.WriteTransaction()
 
1790
        # trigger a write of the inventory store.
 
1791
        control_store.get_weave_or_empty('inventory', transaction)
 
1792
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1793
        # the revision id here is irrelevant: it will not be stored, and cannot
 
1794
        # already exist.
 
1795
        _revision_store.has_revision_id('A', transaction)
 
1796
        _revision_store.get_signature_file(transaction)
 
1797
        return self.open(a_bzrdir=a_bzrdir, _found=True)
 
1798
 
 
1799
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1800
        """See RepositoryFormat.open().
 
1801
        
 
1802
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1803
                                    repository at a slightly different url
 
1804
                                    than normal. I.e. during 'upgrade'.
 
1805
        """
 
1806
        if not _found:
 
1807
            format = RepositoryFormat.find_format(a_bzrdir)
 
1808
            assert format.__class__ ==  self.__class__
 
1809
        if _override_transport is not None:
 
1810
            repo_transport = _override_transport
 
1811
        else:
 
1812
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1813
        control_files = lockable_files.LockableFiles(repo_transport,
 
1814
                                'lock', lockdir.LockDir)
 
1815
        text_store = self._get_text_store(repo_transport, control_files)
 
1816
        control_store = self._get_control_store(repo_transport, control_files)
 
1817
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1818
        return KnitRepository(_format=self,
 
1819
                              a_bzrdir=a_bzrdir,
 
1820
                              control_files=control_files,
 
1821
                              _revision_store=_revision_store,
 
1822
                              control_store=control_store,
 
1823
                              text_store=text_store)
 
1824
 
 
1825
 
 
1826
class RepositoryFormatKnit1(RepositoryFormatKnit):
 
1827
    """Bzr repository knit format 1.
 
1828
 
 
1829
    This repository format has:
 
1830
     - knits for file texts and inventory
 
1831
     - hash subdirectory based stores.
 
1832
     - knits for revisions and signatures
 
1833
     - TextStores for revisions and signatures.
 
1834
     - a format marker of its own
 
1835
     - an optional 'shared-storage' flag
 
1836
     - an optional 'no-working-trees' flag
 
1837
     - a LockDir lock
 
1838
 
 
1839
    This format was introduced in bzr 0.8.
 
1840
    """
 
1841
    def get_format_string(self):
 
1842
        """See RepositoryFormat.get_format_string()."""
 
1843
        return "Bazaar-NG Knit Repository Format 1"
 
1844
 
 
1845
    def get_format_description(self):
 
1846
        """See RepositoryFormat.get_format_description()."""
 
1847
        return "Knit repository format 1"
 
1848
 
 
1849
    def check_conversion_target(self, target_format):
 
1850
        pass
 
1851
 
 
1852
 
 
1853
class RepositoryFormatKnit2(RepositoryFormatKnit):
 
1854
    """Bzr repository knit format 2.
 
1855
 
 
1856
    THIS FORMAT IS EXPERIMENTAL
 
1857
    This repository format has:
 
1858
     - knits for file texts and inventory
 
1859
     - hash subdirectory based stores.
 
1860
     - knits for revisions and signatures
 
1861
     - TextStores for revisions and signatures.
 
1862
     - a format marker of its own
 
1863
     - an optional 'shared-storage' flag
 
1864
     - an optional 'no-working-trees' flag
 
1865
     - a LockDir lock
 
1866
     - Support for recording full info about the tree root
 
1867
     - A tag dictionary stored in the repository, pointing to revisions
 
1868
    """
 
1869
    
 
1870
    rich_root_data = True
 
1871
 
 
1872
    def get_format_string(self):
 
1873
        """See RepositoryFormat.get_format_string()."""
 
1874
        return "Bazaar Knit Repository Format 2\n"
 
1875
 
 
1876
    def get_format_description(self):
 
1877
        """See RepositoryFormat.get_format_description()."""
 
1878
        return "Knit repository format 2"
 
1879
 
 
1880
    def check_conversion_target(self, target_format):
 
1881
        if not target_format.rich_root_data:
 
1882
            raise errors.BadConversionTarget(
 
1883
                'Does not support rich root data.', target_format)
 
1884
 
 
1885
    def open(self, a_bzrdir, _found=False, _override_transport=None):
 
1886
        """See RepositoryFormat.open().
 
1887
        
 
1888
        :param _override_transport: INTERNAL USE ONLY. Allows opening the
 
1889
                                    repository at a slightly different url
 
1890
                                    than normal. I.e. during 'upgrade'.
 
1891
        """
 
1892
        if not _found:
 
1893
            format = RepositoryFormat.find_format(a_bzrdir)
 
1894
            assert format.__class__ ==  self.__class__
 
1895
        if _override_transport is not None:
 
1896
            repo_transport = _override_transport
 
1897
        else:
 
1898
            repo_transport = a_bzrdir.get_repository_transport(None)
 
1899
        control_files = lockable_files.LockableFiles(repo_transport, 'lock',
 
1900
                                                     lockdir.LockDir)
 
1901
        text_store = self._get_text_store(repo_transport, control_files)
 
1902
        control_store = self._get_control_store(repo_transport, control_files)
 
1903
        _revision_store = self._get_revision_store(repo_transport, control_files)
 
1904
        return KnitRepository2(_format=self,
 
1905
                               a_bzrdir=a_bzrdir,
 
1906
                               control_files=control_files,
 
1907
                               _revision_store=_revision_store,
 
1908
                               control_store=control_store,
 
1909
                                text_store=text_store)
 
1910
 
 
1911
    def initialize(self, a_bzrdir, shared=False):
 
1912
        repo = super(RepositoryFormatKnit2, self).initialize(a_bzrdir, shared)
 
1913
        repo._transport.put_bytes_non_atomic('tags', '')
 
1914
        return repo
 
1915
 
 
1916
    def supports_tags(self):
 
1917
        return True
 
1918
 
 
1919
 
 
1920
 
 
1921
# formats which have no format string are not discoverable
 
1922
# and not independently creatable, so are not registered.
 
1923
RepositoryFormat.register_format(RepositoryFormat7())
 
1924
# KEEP in sync with bzrdir.format_registry default
 
1925
_default_format = RepositoryFormatKnit1()
 
1926
RepositoryFormat.register_format(_default_format)
 
1927
RepositoryFormat.register_format(RepositoryFormatKnit2())
 
1928
RepositoryFormat.set_default_format(_default_format)
 
1929
_legacy_formats = [RepositoryFormat4(),
 
1930
                   RepositoryFormat5(),
 
1931
                   RepositoryFormat6()]
 
1932
 
 
1933
 
 
1934
class InterRepository(InterObject):
 
1935
    """This class represents operations taking place between two repositories.
 
1936
 
 
1937
    Its instances have methods like copy_content and fetch, and contain
 
1938
    references to the source and target repositories these operations can be 
 
1939
    carried out on.
 
1940
 
 
1941
    Often we will provide convenience methods on 'repository' which carry out
 
1942
    operations with another repository - they will always forward to
 
1943
    InterRepository.get(other).method_name(parameters).
 
1944
    """
 
1945
 
 
1946
    _optimisers = []
 
1947
    """The available optimised InterRepository types."""
 
1948
 
 
1949
    def copy_content(self, revision_id=None, basis=None):
 
1950
        raise NotImplementedError(self.copy_content)
 
1951
 
 
1952
    def fetch(self, revision_id=None, pb=None):
 
1953
        """Fetch the content required to construct revision_id.
 
1954
 
 
1955
        The content is copied from self.source to self.target.
 
1956
 
 
1957
        :param revision_id: if None all content is copied, if NULL_REVISION no
 
1958
                            content is copied.
 
1959
        :param pb: optional progress bar to use for progress reports. If not
 
1960
                   provided a default one will be created.
 
1961
 
 
1962
        Returns the copied revision count and the failed revisions in a tuple:
 
1963
        (copied, failures).
 
1964
        """
 
1965
        raise NotImplementedError(self.fetch)
 
1966
   
 
1967
    @needs_read_lock
 
1968
    def missing_revision_ids(self, revision_id=None):
 
1969
        """Return the revision ids that source has that target does not.
 
1970
        
 
1971
        These are returned in topological order.
 
1972
 
 
1973
        :param revision_id: only return revision ids included by this
 
1974
                            revision_id.
 
1975
        """
 
1976
        # generic, possibly worst case, slow code path.
 
1977
        target_ids = set(self.target.all_revision_ids())
 
1978
        if revision_id is not None:
 
1979
            source_ids = self.source.get_ancestry(revision_id)
 
1980
            assert source_ids[0] is None
 
1981
            source_ids.pop(0)
 
1982
        else:
 
1983
            source_ids = self.source.all_revision_ids()
 
1984
        result_set = set(source_ids).difference(target_ids)
 
1985
        # this may look like a no-op: its not. It preserves the ordering
 
1986
        # other_ids had while only returning the members from other_ids
 
1987
        # that we've decided we need.
 
1988
        return [rev_id for rev_id in source_ids if rev_id in result_set]
 
1989
 
 
1990
 
 
1991
class InterSameDataRepository(InterRepository):
 
1992
    """Code for converting between repositories that represent the same data.
 
1993
    
 
1994
    Data format and model must match for this to work.
 
1995
    """
 
1996
 
 
1997
    _matching_repo_format = RepositoryFormat4()
 
1998
    """Repository format for testing with."""
 
1999
 
 
2000
    @staticmethod
 
2001
    def is_compatible(source, target):
 
2002
        if not isinstance(source, Repository):
 
2003
            return False
 
2004
        if not isinstance(target, Repository):
 
2005
            return False
 
2006
        if source._format.rich_root_data == target._format.rich_root_data:
 
2007
            return True
 
2008
        else:
 
2009
            return False
 
2010
 
 
2011
    @needs_write_lock
 
2012
    def copy_content(self, revision_id=None, basis=None):
 
2013
        """Make a complete copy of the content in self into destination.
 
2014
        
 
2015
        This is a destructive operation! Do not use it on existing 
 
2016
        repositories.
 
2017
 
 
2018
        :param revision_id: Only copy the content needed to construct
 
2019
                            revision_id and its parents.
 
2020
        :param basis: Copy the needed data preferentially from basis.
 
2021
        """
 
2022
        try:
 
2023
            self.target.set_make_working_trees(self.source.make_working_trees())
 
2024
        except NotImplementedError:
 
2025
            pass
 
2026
        # grab the basis available data
 
2027
        if basis is not None:
 
2028
            self.target.fetch(basis, revision_id=revision_id)
 
2029
        # but don't bother fetching if we have the needed data now.
 
2030
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
 
2031
            self.target.has_revision(revision_id)):
 
2032
            return
 
2033
        self.target.fetch(self.source, revision_id=revision_id)
 
2034
 
 
2035
    @needs_write_lock
 
2036
    def fetch(self, revision_id=None, pb=None):
 
2037
        """See InterRepository.fetch()."""
 
2038
        from bzrlib.fetch import GenericRepoFetcher
 
2039
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2040
               self.source, self.source._format, self.target, 
 
2041
               self.target._format)
 
2042
        f = GenericRepoFetcher(to_repository=self.target,
 
2043
                               from_repository=self.source,
 
2044
                               last_revision=revision_id,
 
2045
                               pb=pb)
 
2046
        return f.count_copied, f.failed_revisions
 
2047
 
 
2048
 
 
2049
class InterWeaveRepo(InterSameDataRepository):
 
2050
    """Optimised code paths between Weave based repositories."""
 
2051
 
 
2052
    _matching_repo_format = RepositoryFormat7()
 
2053
    """Repository format for testing with."""
 
2054
 
 
2055
    @staticmethod
 
2056
    def is_compatible(source, target):
 
2057
        """Be compatible with known Weave formats.
 
2058
        
 
2059
        We don't test for the stores being of specific types because that
 
2060
        could lead to confusing results, and there is no need to be 
 
2061
        overly general.
 
2062
        """
 
2063
        try:
 
2064
            return (isinstance(source._format, (RepositoryFormat5,
 
2065
                                                RepositoryFormat6,
 
2066
                                                RepositoryFormat7)) and
 
2067
                    isinstance(target._format, (RepositoryFormat5,
 
2068
                                                RepositoryFormat6,
 
2069
                                                RepositoryFormat7)))
 
2070
        except AttributeError:
 
2071
            return False
 
2072
    
 
2073
    @needs_write_lock
 
2074
    def copy_content(self, revision_id=None, basis=None):
 
2075
        """See InterRepository.copy_content()."""
 
2076
        # weave specific optimised path:
 
2077
        if basis is not None:
 
2078
            # copy the basis in, then fetch remaining data.
 
2079
            basis.copy_content_into(self.target, revision_id)
 
2080
            # the basis copy_content_into could miss-set this.
 
2081
            try:
 
2082
                self.target.set_make_working_trees(self.source.make_working_trees())
 
2083
            except NotImplementedError:
 
2084
                pass
 
2085
            self.target.fetch(self.source, revision_id=revision_id)
 
2086
        else:
 
2087
            try:
 
2088
                self.target.set_make_working_trees(self.source.make_working_trees())
 
2089
            except NotImplementedError:
 
2090
                pass
 
2091
            # FIXME do not peek!
 
2092
            if self.source.control_files._transport.listable():
 
2093
                pb = ui.ui_factory.nested_progress_bar()
 
2094
                try:
 
2095
                    self.target.weave_store.copy_all_ids(
 
2096
                        self.source.weave_store,
 
2097
                        pb=pb,
 
2098
                        from_transaction=self.source.get_transaction(),
 
2099
                        to_transaction=self.target.get_transaction())
 
2100
                    pb.update('copying inventory', 0, 1)
 
2101
                    self.target.control_weaves.copy_multi(
 
2102
                        self.source.control_weaves, ['inventory'],
 
2103
                        from_transaction=self.source.get_transaction(),
 
2104
                        to_transaction=self.target.get_transaction())
 
2105
                    self.target._revision_store.text_store.copy_all_ids(
 
2106
                        self.source._revision_store.text_store,
 
2107
                        pb=pb)
 
2108
                finally:
 
2109
                    pb.finished()
 
2110
            else:
 
2111
                self.target.fetch(self.source, revision_id=revision_id)
 
2112
 
 
2113
    @needs_write_lock
 
2114
    def fetch(self, revision_id=None, pb=None):
 
2115
        """See InterRepository.fetch()."""
 
2116
        from bzrlib.fetch import GenericRepoFetcher
 
2117
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2118
               self.source, self.source._format, self.target, self.target._format)
 
2119
        f = GenericRepoFetcher(to_repository=self.target,
 
2120
                               from_repository=self.source,
 
2121
                               last_revision=revision_id,
 
2122
                               pb=pb)
 
2123
        return f.count_copied, f.failed_revisions
 
2124
 
 
2125
    @needs_read_lock
 
2126
    def missing_revision_ids(self, revision_id=None):
 
2127
        """See InterRepository.missing_revision_ids()."""
 
2128
        # we want all revisions to satisfy revision_id in source.
 
2129
        # but we don't want to stat every file here and there.
 
2130
        # we want then, all revisions other needs to satisfy revision_id 
 
2131
        # checked, but not those that we have locally.
 
2132
        # so the first thing is to get a subset of the revisions to 
 
2133
        # satisfy revision_id in source, and then eliminate those that
 
2134
        # we do already have. 
 
2135
        # this is slow on high latency connection to self, but as as this
 
2136
        # disk format scales terribly for push anyway due to rewriting 
 
2137
        # inventory.weave, this is considered acceptable.
 
2138
        # - RBC 20060209
 
2139
        if revision_id is not None:
 
2140
            source_ids = self.source.get_ancestry(revision_id)
 
2141
            assert source_ids[0] is None
 
2142
            source_ids.pop(0)
 
2143
        else:
 
2144
            source_ids = self.source._all_possible_ids()
 
2145
        source_ids_set = set(source_ids)
 
2146
        # source_ids is the worst possible case we may need to pull.
 
2147
        # now we want to filter source_ids against what we actually
 
2148
        # have in target, but don't try to check for existence where we know
 
2149
        # we do not have a revision as that would be pointless.
 
2150
        target_ids = set(self.target._all_possible_ids())
 
2151
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
2152
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
2153
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
2154
        required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
 
2155
        if revision_id is not None:
 
2156
            # we used get_ancestry to determine source_ids then we are assured all
 
2157
            # revisions referenced are present as they are installed in topological order.
 
2158
            # and the tip revision was validated by get_ancestry.
 
2159
            return required_topo_revisions
 
2160
        else:
 
2161
            # if we just grabbed the possibly available ids, then 
 
2162
            # we only have an estimate of whats available and need to validate
 
2163
            # that against the revision records.
 
2164
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
 
2165
 
 
2166
 
 
2167
class InterKnitRepo(InterSameDataRepository):
 
2168
    """Optimised code paths between Knit based repositories."""
 
2169
 
 
2170
    _matching_repo_format = RepositoryFormatKnit1()
 
2171
    """Repository format for testing with."""
 
2172
 
 
2173
    @staticmethod
 
2174
    def is_compatible(source, target):
 
2175
        """Be compatible with known Knit formats.
 
2176
        
 
2177
        We don't test for the stores being of specific types because that
 
2178
        could lead to confusing results, and there is no need to be 
 
2179
        overly general.
 
2180
        """
 
2181
        try:
 
2182
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
 
2183
                    isinstance(target._format, (RepositoryFormatKnit1)))
 
2184
        except AttributeError:
 
2185
            return False
 
2186
 
 
2187
    @needs_write_lock
 
2188
    def fetch(self, revision_id=None, pb=None):
 
2189
        """See InterRepository.fetch()."""
 
2190
        from bzrlib.fetch import KnitRepoFetcher
 
2191
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2192
               self.source, self.source._format, self.target, self.target._format)
 
2193
        f = KnitRepoFetcher(to_repository=self.target,
 
2194
                            from_repository=self.source,
 
2195
                            last_revision=revision_id,
 
2196
                            pb=pb)
 
2197
        return f.count_copied, f.failed_revisions
 
2198
 
 
2199
    @needs_read_lock
 
2200
    def missing_revision_ids(self, revision_id=None):
 
2201
        """See InterRepository.missing_revision_ids()."""
 
2202
        if revision_id is not None:
 
2203
            source_ids = self.source.get_ancestry(revision_id)
 
2204
            assert source_ids[0] is None
 
2205
            source_ids.pop(0)
 
2206
        else:
 
2207
            source_ids = self.source._all_possible_ids()
 
2208
        source_ids_set = set(source_ids)
 
2209
        # source_ids is the worst possible case we may need to pull.
 
2210
        # now we want to filter source_ids against what we actually
 
2211
        # have in target, but don't try to check for existence where we know
 
2212
        # we do not have a revision as that would be pointless.
 
2213
        target_ids = set(self.target._all_possible_ids())
 
2214
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
2215
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
2216
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
2217
        required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
 
2218
        if revision_id is not None:
 
2219
            # we used get_ancestry to determine source_ids then we are assured all
 
2220
            # revisions referenced are present as they are installed in topological order.
 
2221
            # and the tip revision was validated by get_ancestry.
 
2222
            return required_topo_revisions
 
2223
        else:
 
2224
            # if we just grabbed the possibly available ids, then 
 
2225
            # we only have an estimate of whats available and need to validate
 
2226
            # that against the revision records.
 
2227
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
 
2228
 
 
2229
 
 
2230
class InterModel1and2(InterRepository):
 
2231
 
 
2232
    _matching_repo_format = None
 
2233
 
 
2234
    @staticmethod
 
2235
    def is_compatible(source, target):
 
2236
        if not isinstance(source, Repository):
 
2237
            return False
 
2238
        if not isinstance(target, Repository):
 
2239
            return False
 
2240
        if not source._format.rich_root_data and target._format.rich_root_data:
 
2241
            return True
 
2242
        else:
 
2243
            return False
 
2244
 
 
2245
    @needs_write_lock
 
2246
    def fetch(self, revision_id=None, pb=None):
 
2247
        """See InterRepository.fetch()."""
 
2248
        from bzrlib.fetch import Model1toKnit2Fetcher
 
2249
        f = Model1toKnit2Fetcher(to_repository=self.target,
 
2250
                                 from_repository=self.source,
 
2251
                                 last_revision=revision_id,
 
2252
                                 pb=pb)
 
2253
        return f.count_copied, f.failed_revisions
 
2254
 
 
2255
    @needs_write_lock
 
2256
    def copy_content(self, revision_id=None, basis=None):
 
2257
        """Make a complete copy of the content in self into destination.
 
2258
        
 
2259
        This is a destructive operation! Do not use it on existing 
 
2260
        repositories.
 
2261
 
 
2262
        :param revision_id: Only copy the content needed to construct
 
2263
                            revision_id and its parents.
 
2264
        :param basis: Copy the needed data preferentially from basis.
 
2265
        """
 
2266
        try:
 
2267
            self.target.set_make_working_trees(self.source.make_working_trees())
 
2268
        except NotImplementedError:
 
2269
            pass
 
2270
        # grab the basis available data
 
2271
        if basis is not None:
 
2272
            self.target.fetch(basis, revision_id=revision_id)
 
2273
        # but don't bother fetching if we have the needed data now.
 
2274
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
 
2275
            self.target.has_revision(revision_id)):
 
2276
            return
 
2277
        self.target.fetch(self.source, revision_id=revision_id)
 
2278
 
 
2279
 
 
2280
class InterKnit1and2(InterKnitRepo):
 
2281
 
 
2282
    _matching_repo_format = None
 
2283
 
 
2284
    @staticmethod
 
2285
    def is_compatible(source, target):
 
2286
        """Be compatible with Knit1 source and Knit2 target"""
 
2287
        try:
 
2288
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
 
2289
                    isinstance(target._format, (RepositoryFormatKnit2)))
 
2290
        except AttributeError:
 
2291
            return False
 
2292
 
 
2293
    @needs_write_lock
 
2294
    def fetch(self, revision_id=None, pb=None):
 
2295
        """See InterRepository.fetch()."""
 
2296
        from bzrlib.fetch import Knit1to2Fetcher
 
2297
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2298
               self.source, self.source._format, self.target, 
 
2299
               self.target._format)
 
2300
        f = Knit1to2Fetcher(to_repository=self.target,
 
2301
                            from_repository=self.source,
 
2302
                            last_revision=revision_id,
 
2303
                            pb=pb)
 
2304
        return f.count_copied, f.failed_revisions
 
2305
 
 
2306
 
 
2307
InterRepository.register_optimiser(InterSameDataRepository)
 
2308
InterRepository.register_optimiser(InterWeaveRepo)
 
2309
InterRepository.register_optimiser(InterKnitRepo)
 
2310
InterRepository.register_optimiser(InterModel1and2)
 
2311
InterRepository.register_optimiser(InterKnit1and2)
 
2312
 
 
2313
 
 
2314
class RepositoryTestProviderAdapter(object):
 
2315
    """A tool to generate a suite testing multiple repository formats at once.
 
2316
 
 
2317
    This is done by copying the test once for each transport and injecting
 
2318
    the transport_server, transport_readonly_server, and bzrdir_format and
 
2319
    repository_format classes into each copy. Each copy is also given a new id()
 
2320
    to make it easy to identify.
 
2321
    """
 
2322
 
 
2323
    def __init__(self, transport_server, transport_readonly_server, formats):
 
2324
        self._transport_server = transport_server
 
2325
        self._transport_readonly_server = transport_readonly_server
 
2326
        self._formats = formats
 
2327
    
 
2328
    def adapt(self, test):
 
2329
        result = unittest.TestSuite()
 
2330
        for repository_format, bzrdir_format in self._formats:
 
2331
            new_test = deepcopy(test)
 
2332
            new_test.transport_server = self._transport_server
 
2333
            new_test.transport_readonly_server = self._transport_readonly_server
 
2334
            new_test.bzrdir_format = bzrdir_format
 
2335
            new_test.repository_format = repository_format
 
2336
            def make_new_test_id():
 
2337
                new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
 
2338
                return lambda: new_id
 
2339
            new_test.id = make_new_test_id()
 
2340
            result.addTest(new_test)
 
2341
        return result
 
2342
 
 
2343
 
 
2344
class InterRepositoryTestProviderAdapter(object):
 
2345
    """A tool to generate a suite testing multiple inter repository formats.
 
2346
 
 
2347
    This is done by copying the test once for each interrepo provider and injecting
 
2348
    the transport_server, transport_readonly_server, repository_format and 
 
2349
    repository_to_format classes into each copy.
 
2350
    Each copy is also given a new id() to make it easy to identify.
 
2351
    """
 
2352
 
 
2353
    def __init__(self, transport_server, transport_readonly_server, formats):
 
2354
        self._transport_server = transport_server
 
2355
        self._transport_readonly_server = transport_readonly_server
 
2356
        self._formats = formats
 
2357
    
 
2358
    def adapt(self, test):
 
2359
        result = unittest.TestSuite()
 
2360
        for interrepo_class, repository_format, repository_format_to in self._formats:
 
2361
            new_test = deepcopy(test)
 
2362
            new_test.transport_server = self._transport_server
 
2363
            new_test.transport_readonly_server = self._transport_readonly_server
 
2364
            new_test.interrepo_class = interrepo_class
 
2365
            new_test.repository_format = repository_format
 
2366
            new_test.repository_format_to = repository_format_to
 
2367
            def make_new_test_id():
 
2368
                new_id = "%s(%s)" % (new_test.id(), interrepo_class.__name__)
 
2369
                return lambda: new_id
 
2370
            new_test.id = make_new_test_id()
 
2371
            result.addTest(new_test)
 
2372
        return result
 
2373
 
 
2374
    @staticmethod
 
2375
    def default_test_list():
 
2376
        """Generate the default list of interrepo permutations to test."""
 
2377
        result = []
 
2378
        # test the default InterRepository between format 6 and the current 
 
2379
        # default format.
 
2380
        # XXX: robertc 20060220 reinstate this when there are two supported
 
2381
        # formats which do not have an optimal code path between them.
 
2382
        #result.append((InterRepository,
 
2383
        #               RepositoryFormat6(),
 
2384
        #               RepositoryFormatKnit1()))
 
2385
        for optimiser in InterRepository._optimisers:
 
2386
            if optimiser._matching_repo_format is not None:
 
2387
                result.append((optimiser,
 
2388
                               optimiser._matching_repo_format,
 
2389
                               optimiser._matching_repo_format
 
2390
                               ))
 
2391
        # if there are specific combinations we want to use, we can add them 
 
2392
        # here.
 
2393
        result.append((InterModel1and2, RepositoryFormat5(),
 
2394
                       RepositoryFormatKnit2()))
 
2395
        result.append((InterKnit1and2, RepositoryFormatKnit1(),
 
2396
                       RepositoryFormatKnit2()))
 
2397
        return result
 
2398
 
 
2399
 
 
2400
class CopyConverter(object):
 
2401
    """A repository conversion tool which just performs a copy of the content.
 
2402
    
 
2403
    This is slow but quite reliable.
 
2404
    """
 
2405
 
 
2406
    def __init__(self, target_format):
 
2407
        """Create a CopyConverter.
 
2408
 
 
2409
        :param target_format: The format the resulting repository should be.
 
2410
        """
 
2411
        self.target_format = target_format
 
2412
        
 
2413
    def convert(self, repo, pb):
 
2414
        """Perform the conversion of to_convert, giving feedback via pb.
 
2415
 
 
2416
        :param to_convert: The disk object to convert.
 
2417
        :param pb: a progress bar to use for progress information.
 
2418
        """
 
2419
        self.pb = pb
 
2420
        self.count = 0
 
2421
        self.total = 4
 
2422
        # this is only useful with metadir layouts - separated repo content.
 
2423
        # trigger an assertion if not such
 
2424
        repo._format.get_format_string()
 
2425
        self.repo_dir = repo.bzrdir
 
2426
        self.step('Moving repository to repository.backup')
 
2427
        self.repo_dir.transport.move('repository', 'repository.backup')
 
2428
        backup_transport =  self.repo_dir.transport.clone('repository.backup')
 
2429
        repo._format.check_conversion_target(self.target_format)
 
2430
        self.source_repo = repo._format.open(self.repo_dir,
 
2431
            _found=True,
 
2432
            _override_transport=backup_transport)
 
2433
        self.step('Creating new repository')
 
2434
        converted = self.target_format.initialize(self.repo_dir,
 
2435
                                                  self.source_repo.is_shared())
 
2436
        converted.lock_write()
 
2437
        try:
 
2438
            self.step('Copying content into repository.')
 
2439
            self.source_repo.copy_content_into(converted)
 
2440
        finally:
 
2441
            converted.unlock()
 
2442
        self.step('Deleting old repository content.')
 
2443
        self.repo_dir.transport.delete_tree('repository.backup')
 
2444
        self.pb.note('repository converted')
 
2445
 
 
2446
    def step(self, message):
 
2447
        """Update the pb by a step."""
 
2448
        self.count +=1
 
2449
        self.pb.update(message, self.count, self.total)
 
2450
 
 
2451
 
 
2452
class CommitBuilder(object):
 
2453
    """Provides an interface to build up a commit.
 
2454
 
 
2455
    This allows describing a tree to be committed without needing to 
 
2456
    know the internals of the format of the repository.
 
2457
    """
 
2458
    
 
2459
    record_root_entry = False
 
2460
    def __init__(self, repository, parents, config, timestamp=None, 
 
2461
                 timezone=None, committer=None, revprops=None, 
 
2462
                 revision_id=None):
 
2463
        """Initiate a CommitBuilder.
 
2464
 
 
2465
        :param repository: Repository to commit to.
 
2466
        :param parents: Revision ids of the parents of the new revision.
 
2467
        :param config: Configuration to use.
 
2468
        :param timestamp: Optional timestamp recorded for commit.
 
2469
        :param timezone: Optional timezone for timestamp.
 
2470
        :param committer: Optional committer to set for commit.
 
2471
        :param revprops: Optional dictionary of revision properties.
 
2472
        :param revision_id: Optional revision id.
 
2473
        """
 
2474
        self._config = config
 
2475
 
 
2476
        if committer is None:
 
2477
            self._committer = self._config.username()
 
2478
        else:
 
2479
            assert isinstance(committer, basestring), type(committer)
 
2480
            self._committer = committer
 
2481
 
 
2482
        self.new_inventory = Inventory(None)
 
2483
        self._new_revision_id = revision_id
 
2484
        self.parents = parents
 
2485
        self.repository = repository
 
2486
 
 
2487
        self._revprops = {}
 
2488
        if revprops is not None:
 
2489
            self._revprops.update(revprops)
 
2490
 
 
2491
        if timestamp is None:
 
2492
            timestamp = time.time()
 
2493
        # Restrict resolution to 1ms
 
2494
        self._timestamp = round(timestamp, 3)
 
2495
 
 
2496
        if timezone is None:
 
2497
            self._timezone = local_time_offset()
 
2498
        else:
 
2499
            self._timezone = int(timezone)
 
2500
 
 
2501
        self._generate_revision_if_needed()
 
2502
 
 
2503
    def commit(self, message):
 
2504
        """Make the actual commit.
 
2505
 
 
2506
        :return: The revision id of the recorded revision.
 
2507
        """
 
2508
        rev = _mod_revision.Revision(
 
2509
                       timestamp=self._timestamp,
 
2510
                       timezone=self._timezone,
 
2511
                       committer=self._committer,
 
2512
                       message=message,
 
2513
                       inventory_sha1=self.inv_sha1,
 
2514
                       revision_id=self._new_revision_id,
 
2515
                       properties=self._revprops)
 
2516
        rev.parent_ids = self.parents
 
2517
        self.repository.add_revision(self._new_revision_id, rev, 
 
2518
            self.new_inventory, self._config)
 
2519
        return self._new_revision_id
 
2520
 
 
2521
    def revision_tree(self):
 
2522
        """Return the tree that was just committed.
 
2523
 
 
2524
        After calling commit() this can be called to get a RevisionTree
 
2525
        representing the newly committed tree. This is preferred to
 
2526
        calling Repository.revision_tree() because that may require
 
2527
        deserializing the inventory, while we already have a copy in
 
2528
        memory.
 
2529
        """
 
2530
        return RevisionTree(self.repository, self.new_inventory,
 
2531
                            self._new_revision_id)
 
2532
 
 
2533
    def finish_inventory(self):
 
2534
        """Tell the builder that the inventory is finished."""
 
2535
        if self.new_inventory.root is None:
 
2536
            symbol_versioning.warn('Root entry should be supplied to'
 
2537
                ' record_entry_contents, as of bzr 0.10.',
 
2538
                 DeprecationWarning, stacklevel=2)
 
2539
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
 
2540
        self.new_inventory.revision_id = self._new_revision_id
 
2541
        self.inv_sha1 = self.repository.add_inventory(
 
2542
            self._new_revision_id,
 
2543
            self.new_inventory,
 
2544
            self.parents
 
2545
            )
 
2546
 
 
2547
    def _gen_revision_id(self):
 
2548
        """Return new revision-id."""
 
2549
        return generate_ids.gen_revision_id(self._config.username(),
 
2550
                                            self._timestamp)
 
2551
 
 
2552
    def _generate_revision_if_needed(self):
 
2553
        """Create a revision id if None was supplied.
 
2554
        
 
2555
        If the repository can not support user-specified revision ids
 
2556
        they should override this function and raise CannotSetRevisionId
 
2557
        if _new_revision_id is not None.
 
2558
 
 
2559
        :raises: CannotSetRevisionId
 
2560
        """
 
2561
        if self._new_revision_id is None:
 
2562
            self._new_revision_id = self._gen_revision_id()
 
2563
 
 
2564
    def record_entry_contents(self, ie, parent_invs, path, tree):
 
2565
        """Record the content of ie from tree into the commit if needed.
 
2566
 
 
2567
        Side effect: sets ie.revision when unchanged
 
2568
 
 
2569
        :param ie: An inventory entry present in the commit.
 
2570
        :param parent_invs: The inventories of the parent revisions of the
 
2571
            commit.
 
2572
        :param path: The path the entry is at in the tree.
 
2573
        :param tree: The tree which contains this entry and should be used to 
 
2574
        obtain content.
 
2575
        """
 
2576
        if self.new_inventory.root is None and ie.parent_id is not None:
 
2577
            symbol_versioning.warn('Root entry should be supplied to'
 
2578
                ' record_entry_contents, as of bzr 0.10.',
 
2579
                 DeprecationWarning, stacklevel=2)
 
2580
            self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
 
2581
                                       '', tree)
 
2582
        self.new_inventory.add(ie)
 
2583
 
 
2584
        # ie.revision is always None if the InventoryEntry is considered
 
2585
        # for committing. ie.snapshot will record the correct revision 
 
2586
        # which may be the sole parent if it is untouched.
 
2587
        if ie.revision is not None:
 
2588
            return
 
2589
 
 
2590
        # In this revision format, root entries have no knit or weave
 
2591
        if ie is self.new_inventory.root:
 
2592
            # When serializing out to disk and back in
 
2593
            # root.revision is always _new_revision_id
 
2594
            ie.revision = self._new_revision_id
 
2595
            return
 
2596
        previous_entries = ie.find_previous_heads(
 
2597
            parent_invs,
 
2598
            self.repository.weave_store,
 
2599
            self.repository.get_transaction())
 
2600
        # we are creating a new revision for ie in the history store
 
2601
        # and inventory.
 
2602
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
 
2603
 
 
2604
    def modified_directory(self, file_id, file_parents):
 
2605
        """Record the presence of a symbolic link.
 
2606
 
 
2607
        :param file_id: The file_id of the link to record.
 
2608
        :param file_parents: The per-file parent revision ids.
 
2609
        """
 
2610
        self._add_text_to_weave(file_id, [], file_parents.keys())
 
2611
    
 
2612
    def modified_file_text(self, file_id, file_parents,
 
2613
                           get_content_byte_lines, text_sha1=None,
 
2614
                           text_size=None):
 
2615
        """Record the text of file file_id
 
2616
 
 
2617
        :param file_id: The file_id of the file to record the text of.
 
2618
        :param file_parents: The per-file parent revision ids.
 
2619
        :param get_content_byte_lines: A callable which will return the byte
 
2620
            lines for the file.
 
2621
        :param text_sha1: Optional SHA1 of the file contents.
 
2622
        :param text_size: Optional size of the file contents.
 
2623
        """
 
2624
        # mutter('storing text of file {%s} in revision {%s} into %r',
 
2625
        #        file_id, self._new_revision_id, self.repository.weave_store)
 
2626
        # special case to avoid diffing on renames or 
 
2627
        # reparenting
 
2628
        if (len(file_parents) == 1
 
2629
            and text_sha1 == file_parents.values()[0].text_sha1
 
2630
            and text_size == file_parents.values()[0].text_size):
 
2631
            previous_ie = file_parents.values()[0]
 
2632
            versionedfile = self.repository.weave_store.get_weave(file_id, 
 
2633
                self.repository.get_transaction())
 
2634
            versionedfile.clone_text(self._new_revision_id, 
 
2635
                previous_ie.revision, file_parents.keys())
 
2636
            return text_sha1, text_size
 
2637
        else:
 
2638
            new_lines = get_content_byte_lines()
 
2639
            # TODO: Rather than invoking sha_strings here, _add_text_to_weave
 
2640
            # should return the SHA1 and size
 
2641
            self._add_text_to_weave(file_id, new_lines, file_parents.keys())
 
2642
            return osutils.sha_strings(new_lines), \
 
2643
                sum(map(len, new_lines))
 
2644
 
 
2645
    def modified_link(self, file_id, file_parents, link_target):
 
2646
        """Record the presence of a symbolic link.
 
2647
 
 
2648
        :param file_id: The file_id of the link to record.
 
2649
        :param file_parents: The per-file parent revision ids.
 
2650
        :param link_target: Target location of this link.
 
2651
        """
 
2652
        self._add_text_to_weave(file_id, [], file_parents.keys())
 
2653
 
 
2654
    def _add_text_to_weave(self, file_id, new_lines, parents):
 
2655
        versionedfile = self.repository.weave_store.get_weave_or_empty(
 
2656
            file_id, self.repository.get_transaction())
 
2657
        versionedfile.add_lines(self._new_revision_id, parents, new_lines)
 
2658
        versionedfile.clear_cache()
 
2659
 
 
2660
 
 
2661
class _CommitBuilder(CommitBuilder):
 
2662
    """Temporary class so old CommitBuilders are detected properly
 
2663
    
 
2664
    Note: CommitBuilder works whether or not root entry is recorded.
 
2665
    """
 
2666
 
 
2667
    record_root_entry = True
 
2668
 
 
2669
 
 
2670
class RootCommitBuilder(CommitBuilder):
 
2671
    """This commitbuilder actually records the root id"""
 
2672
    
 
2673
    record_root_entry = True
 
2674
 
 
2675
    def record_entry_contents(self, ie, parent_invs, path, tree):
 
2676
        """Record the content of ie from tree into the commit if needed.
 
2677
 
 
2678
        Side effect: sets ie.revision when unchanged
 
2679
 
 
2680
        :param ie: An inventory entry present in the commit.
 
2681
        :param parent_invs: The inventories of the parent revisions of the
 
2682
            commit.
 
2683
        :param path: The path the entry is at in the tree.
 
2684
        :param tree: The tree which contains this entry and should be used to 
 
2685
        obtain content.
 
2686
        """
 
2687
        assert self.new_inventory.root is not None or ie.parent_id is None
 
2688
        self.new_inventory.add(ie)
 
2689
 
 
2690
        # ie.revision is always None if the InventoryEntry is considered
 
2691
        # for committing. ie.snapshot will record the correct revision 
 
2692
        # which may be the sole parent if it is untouched.
 
2693
        if ie.revision is not None:
 
2694
            return
 
2695
 
 
2696
        previous_entries = ie.find_previous_heads(
 
2697
            parent_invs,
 
2698
            self.repository.weave_store,
 
2699
            self.repository.get_transaction())
 
2700
        # we are creating a new revision for ie in the history store
 
2701
        # and inventory.
 
2702
        ie.snapshot(self._new_revision_id, path, previous_entries, tree, self)
 
2703
 
 
2704
 
 
2705
_unescape_map = {
 
2706
    'apos':"'",
 
2707
    'quot':'"',
 
2708
    'amp':'&',
 
2709
    'lt':'<',
 
2710
    'gt':'>'
 
2711
}
 
2712
 
 
2713
 
 
2714
def _unescaper(match, _map=_unescape_map):
 
2715
    return _map[match.group(1)]
 
2716
 
 
2717
 
 
2718
_unescape_re = None
 
2719
 
 
2720
 
 
2721
def _unescape_xml(data):
 
2722
    """Unescape predefined XML entities in a string of data."""
 
2723
    global _unescape_re
 
2724
    if _unescape_re is None:
 
2725
        _unescape_re = re.compile('\&([^;]*);')
 
2726
    return _unescape_re.sub(_unescaper, data)