/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-21 12:42:13 UTC
  • mto: (2220.2.19 tags)
  • mto: This revision was merged to the branch mainline in revision 2309.
  • Revision ID: mbp@sourcefrog.net-20070121124213-8ksq92nrm3tzn6s9
Add -d option to push, pull, merge commands.

Tags now propagate between repositories by commands that move history around:
branch, push, pull, merge.

Add Repository.supports_tags method.

All Repository tag methods now raise TagsNotSupported if they're not.

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