/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: Robert Collins
  • Date: 2007-09-27 21:11:38 UTC
  • mfrom: (2871 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2879.
  • Revision ID: robertc@robertcollins.net-20070927211138-ebsu1bo1qz9f1w8n
Merge bzr.dev.

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
import re
 
22
import time
 
23
 
 
24
from bzrlib import (
 
25
    bzrdir,
 
26
    check,
 
27
    debug,
 
28
    deprecated_graph,
 
29
    errors,
 
30
    generate_ids,
 
31
    gpg,
 
32
    graph,
 
33
    lazy_regex,
 
34
    lockable_files,
 
35
    lockdir,
 
36
    osutils,
 
37
    registry,
 
38
    remote,
 
39
    revision as _mod_revision,
 
40
    symbol_versioning,
 
41
    transactions,
 
42
    ui,
 
43
    )
 
44
from bzrlib.bundle import serializer
 
45
from bzrlib.revisiontree import RevisionTree
 
46
from bzrlib.store.versioned import VersionedFileStore
 
47
from bzrlib.store.text import TextStore
 
48
from bzrlib.testament import Testament
 
49
""")
 
50
 
 
51
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
52
from bzrlib.inter import InterObject
 
53
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
 
54
from bzrlib.symbol_versioning import (
 
55
        deprecated_method,
 
56
        )
 
57
from bzrlib.trace import mutter, mutter_callsite, note, warning
 
58
 
 
59
 
 
60
# Old formats display a warning, but only once
 
61
_deprecation_warning_done = False
 
62
 
 
63
 
 
64
class CommitBuilder(object):
 
65
    """Provides an interface to build up a commit.
 
66
 
 
67
    This allows describing a tree to be committed without needing to 
 
68
    know the internals of the format of the repository.
 
69
    """
 
70
    
 
71
    # all clients should supply tree roots.
 
72
    record_root_entry = True
 
73
    # the default CommitBuilder does not manage trees whose root is versioned.
 
74
    _versioned_root = False
 
75
 
 
76
    def __init__(self, repository, parents, config, timestamp=None, 
 
77
                 timezone=None, committer=None, revprops=None, 
 
78
                 revision_id=None):
 
79
        """Initiate a CommitBuilder.
 
80
 
 
81
        :param repository: Repository to commit to.
 
82
        :param parents: Revision ids of the parents of the new revision.
 
83
        :param config: Configuration to use.
 
84
        :param timestamp: Optional timestamp recorded for commit.
 
85
        :param timezone: Optional timezone for timestamp.
 
86
        :param committer: Optional committer to set for commit.
 
87
        :param revprops: Optional dictionary of revision properties.
 
88
        :param revision_id: Optional revision id.
 
89
        """
 
90
        self._config = config
 
91
 
 
92
        if committer is None:
 
93
            self._committer = self._config.username()
 
94
        else:
 
95
            assert isinstance(committer, basestring), type(committer)
 
96
            self._committer = committer
 
97
 
 
98
        self.new_inventory = Inventory(None)
 
99
        self._new_revision_id = osutils.safe_revision_id(revision_id)
 
100
        self.parents = parents
 
101
        self.repository = repository
 
102
 
 
103
        self._revprops = {}
 
104
        if revprops is not None:
 
105
            self._revprops.update(revprops)
 
106
 
 
107
        if timestamp is None:
 
108
            timestamp = time.time()
 
109
        # Restrict resolution to 1ms
 
110
        self._timestamp = round(timestamp, 3)
 
111
 
 
112
        if timezone is None:
 
113
            self._timezone = osutils.local_time_offset()
 
114
        else:
 
115
            self._timezone = int(timezone)
 
116
 
 
117
        self._generate_revision_if_needed()
 
118
 
 
119
    def commit(self, message):
 
120
        """Make the actual commit.
 
121
 
 
122
        :return: The revision id of the recorded revision.
 
123
        """
 
124
        rev = _mod_revision.Revision(
 
125
                       timestamp=self._timestamp,
 
126
                       timezone=self._timezone,
 
127
                       committer=self._committer,
 
128
                       message=message,
 
129
                       inventory_sha1=self.inv_sha1,
 
130
                       revision_id=self._new_revision_id,
 
131
                       properties=self._revprops)
 
132
        rev.parent_ids = self.parents
 
133
        self.repository.add_revision(self._new_revision_id, rev,
 
134
            self.new_inventory, self._config)
 
135
        self.repository.commit_write_group()
 
136
        return self._new_revision_id
 
137
 
 
138
    def abort(self):
 
139
        """Abort the commit that is being built.
 
140
        """
 
141
        self.repository.abort_write_group()
 
142
 
 
143
    def revision_tree(self):
 
144
        """Return the tree that was just committed.
 
145
 
 
146
        After calling commit() this can be called to get a RevisionTree
 
147
        representing the newly committed tree. This is preferred to
 
148
        calling Repository.revision_tree() because that may require
 
149
        deserializing the inventory, while we already have a copy in
 
150
        memory.
 
151
        """
 
152
        return RevisionTree(self.repository, self.new_inventory,
 
153
                            self._new_revision_id)
 
154
 
 
155
    def finish_inventory(self):
 
156
        """Tell the builder that the inventory is finished."""
 
157
        if self.new_inventory.root is None:
 
158
            symbol_versioning.warn('Root entry should be supplied to'
 
159
                ' record_entry_contents, as of bzr 0.10.',
 
160
                 DeprecationWarning, stacklevel=2)
 
161
            self.new_inventory.add(InventoryDirectory(ROOT_ID, '', None))
 
162
        self.new_inventory.revision_id = self._new_revision_id
 
163
        self.inv_sha1 = self.repository.add_inventory(
 
164
            self._new_revision_id,
 
165
            self.new_inventory,
 
166
            self.parents
 
167
            )
 
168
 
 
169
    def _gen_revision_id(self):
 
170
        """Return new revision-id."""
 
171
        return generate_ids.gen_revision_id(self._config.username(),
 
172
                                            self._timestamp)
 
173
 
 
174
    def _generate_revision_if_needed(self):
 
175
        """Create a revision id if None was supplied.
 
176
        
 
177
        If the repository can not support user-specified revision ids
 
178
        they should override this function and raise CannotSetRevisionId
 
179
        if _new_revision_id is not None.
 
180
 
 
181
        :raises: CannotSetRevisionId
 
182
        """
 
183
        if self._new_revision_id is None:
 
184
            self._new_revision_id = self._gen_revision_id()
 
185
            self.random_revid = True
 
186
        else:
 
187
            self.random_revid = False
 
188
 
 
189
    def _check_root(self, ie, parent_invs, tree):
 
190
        """Helper for record_entry_contents.
 
191
 
 
192
        :param ie: An entry being added.
 
193
        :param parent_invs: The inventories of the parent revisions of the
 
194
            commit.
 
195
        :param tree: The tree that is being committed.
 
196
        """
 
197
        if ie.parent_id is not None:
 
198
            # if ie is not root, add a root automatically.
 
199
            symbol_versioning.warn('Root entry should be supplied to'
 
200
                ' record_entry_contents, as of bzr 0.10.',
 
201
                 DeprecationWarning, stacklevel=2)
 
202
            self.record_entry_contents(tree.inventory.root.copy(), parent_invs,
 
203
                                       '', tree, tree.path_content_summary(''))
 
204
        else:
 
205
            # In this revision format, root entries have no knit or weave When
 
206
            # serializing out to disk and back in root.revision is always
 
207
            # _new_revision_id
 
208
            ie.revision = self._new_revision_id
 
209
 
 
210
    def record_entry_contents(self, ie, parent_invs, path, tree,
 
211
        content_summary):
 
212
        """Record the content of ie from tree into the commit if needed.
 
213
 
 
214
        Side effect: sets ie.revision when unchanged
 
215
 
 
216
        :param ie: An inventory entry present in the commit.
 
217
        :param parent_invs: The inventories of the parent revisions of the
 
218
            commit.
 
219
        :param path: The path the entry is at in the tree.
 
220
        :param tree: The tree which contains this entry and should be used to 
 
221
            obtain content.
 
222
        :param content_summary: Summary data from the tree about the paths
 
223
            content - stat, length, exec, sha/link target. This is only
 
224
            accessed when the entry has a revision of None - that is when it is
 
225
            a candidate to commit.
 
226
        :return: True if a new version of the entry has been recorded.
 
227
            (Committing a merge where a file was only changed on the other side
 
228
            will not return True.)
 
229
        """
 
230
        if self.new_inventory.root is None:
 
231
            self._check_root(ie, parent_invs, tree)
 
232
        if ie.revision is None:
 
233
            kind = content_summary[0]
 
234
        else:
 
235
            # ie is carried over from a prior commit
 
236
            kind = ie.kind
 
237
        # XXX: repository specific check for nested tree support goes here - if
 
238
        # the repo doesn't want nested trees we skip it ?
 
239
        if (kind == 'tree-reference' and
 
240
            not self.repository._format.supports_tree_reference):
 
241
            # mismatch between commit builder logic and repository:
 
242
            # this needs the entry creation pushed down into the builder.
 
243
            raise NotImplementedError
 
244
        # transitional assert only, will remove before release.
 
245
        assert ie.kind == kind
 
246
        self.new_inventory.add(ie)
 
247
 
 
248
        # ie.revision is always None if the InventoryEntry is considered
 
249
        # for committing. We may record the previous parents revision if the
 
250
        # content is actually unchanged against a sole head.
 
251
        if ie.revision is not None:
 
252
            return ie.revision == self._new_revision_id and (path != '' or
 
253
                self._versioned_root)
 
254
 
 
255
        # XXX: Friction: parent_candidates should return a list not a dict
 
256
        #      so that we don't have to walk the inventories again.
 
257
        parent_candiate_entries = ie.parent_candidates(parent_invs)
 
258
        head_set = self.repository.get_graph().heads(parent_candiate_entries.keys())
 
259
        heads = []
 
260
        for inv in parent_invs:
 
261
            if ie.file_id in inv:
 
262
                old_rev = inv[ie.file_id].revision
 
263
                if old_rev in head_set:
 
264
                    heads.append(inv[ie.file_id].revision)
 
265
                    head_set.remove(inv[ie.file_id].revision)
 
266
 
 
267
        store = False
 
268
        # now we check to see if we need to write a new record to the
 
269
        # file-graph.
 
270
        # We write a new entry unless there is one head to the ancestors, and
 
271
        # the kind-derived content is unchanged.
 
272
 
 
273
        # Cheapest check first: no ancestors, or more the one head in the
 
274
        # ancestors, we write a new node.
 
275
        if len(heads) != 1:
 
276
            store = True
 
277
        if not store:
 
278
            # There is a single head, look it up for comparison
 
279
            parent_entry = parent_candiate_entries[heads[0]]
 
280
            # if the non-content specific data has changed, we'll be writing a
 
281
            # node:
 
282
            if (parent_entry.parent_id != ie.parent_id or
 
283
                parent_entry.name != ie.name):
 
284
                store = True
 
285
        # now we need to do content specific checks:
 
286
        if not store:
 
287
            # if the kind changed the content obviously has
 
288
            if kind != parent_entry.kind:
 
289
                store = True
 
290
        if kind == 'file':
 
291
            if not store:
 
292
                if (# if the file length changed we have to store:
 
293
                    parent_entry.text_size != content_summary[1] or
 
294
                    # if the exec bit has changed we have to store:
 
295
                    parent_entry.executable != content_summary[2]):
 
296
                    store = True
 
297
                elif parent_entry.text_sha1 == content_summary[3]:
 
298
                    # all meta and content is unchanged (using a hash cache
 
299
                    # hit to check the sha)
 
300
                    ie.revision = parent_entry.revision
 
301
                    ie.text_size = parent_entry.text_size
 
302
                    ie.text_sha1 = parent_entry.text_sha1
 
303
                    ie.executable = parent_entry.executable
 
304
                    return
 
305
                else:
 
306
                    # Either there is only a hash change(no hash cache entry,
 
307
                    # or same size content change), or there is no change on
 
308
                    # this file at all.
 
309
                    # There is a race condition when inserting content into the
 
310
                    # knit though that can result in different content being
 
311
                    # inserted so even though we may have had a hash cache hit
 
312
                    # here we still tell the store the hash we would *not*
 
313
                    # store a new text on, which means that it can avoid for us
 
314
                    # without a race condition and without double-shaing the
 
315
                    # lines.
 
316
                    nostore_sha = parent_entry.text_sha1
 
317
            if store:
 
318
                nostore_sha = None
 
319
            try:
 
320
                ie.executable = content_summary[2]
 
321
                lines = tree.get_file(ie.file_id, path).readlines()
 
322
                ie.text_sha1, ie.text_size = self._add_text_to_weave(
 
323
                    ie.file_id, lines, heads, nostore_sha)
 
324
            except errors.ExistingContent:
 
325
                # we are not going to store a new file graph node as it turns
 
326
                # out to be unchanged.
 
327
                ie.revision = parent_entry.revision
 
328
                ie.text_size = parent_entry.text_size
 
329
                ie.text_sha1 = parent_entry.text_sha1
 
330
                ie.executable = parent_entry.executable
 
331
                return
 
332
        elif kind == 'directory':
 
333
            if not store:
 
334
                # all data is meta here, nothing specific to directory, so
 
335
                # carry over:
 
336
                ie.revision = parent_entry.revision
 
337
                return
 
338
            lines = []
 
339
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
340
        elif kind == 'symlink':
 
341
            current_link_target = content_summary[3]
 
342
            if not store:
 
343
                # symmlink target is not generic metadata, check if it has
 
344
                # changed.
 
345
                if current_link_target != parent_entry.symlink_target:
 
346
                    store = True
 
347
            if not store:
 
348
                # unchanged, carry over.
 
349
                ie.revision = parent_entry.revision
 
350
                ie.symlink_target = parent_entry.symlink_target
 
351
                return
 
352
            ie.symlink_target = current_link_target
 
353
            lines = []
 
354
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
355
        elif kind == 'tree-reference':
 
356
            if not store:
 
357
                if content_summary[3] != parent_entry.reference_revision:
 
358
                    store = True
 
359
            if not store:
 
360
                # unchanged, carry over.
 
361
                ie.reference_revision = parent_entry.reference_revision
 
362
                ie.revision = parent_entry.revision
 
363
                return
 
364
            ie.reference_revision = content_summary[3]
 
365
            lines = []
 
366
            self._add_text_to_weave(ie.file_id, lines, heads, None)
 
367
        else:
 
368
            raise NotImplementedError('unknown kind')
 
369
        ie.revision = self._new_revision_id
 
370
        return True
 
371
 
 
372
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
 
373
        versionedfile = self.repository.weave_store.get_weave_or_empty(
 
374
            file_id, self.repository.get_transaction())
 
375
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
 
376
        # than add_lines, and allows committing when a parent is ghosted for
 
377
        # some reason.
 
378
        # Note: as we read the content directly from the tree, we know its not
 
379
        # been turned into unicode or badly split - but a broken tree
 
380
        # implementation could give us bad output from readlines() so this is
 
381
        # not a guarantee of safety. What would be better is always checking
 
382
        # the content during test suite execution. RBC 20070912
 
383
        try:
 
384
            return versionedfile.add_lines_with_ghosts(
 
385
                self._new_revision_id, parents, new_lines,
 
386
                nostore_sha=nostore_sha, random_id=self.random_revid,
 
387
                check_content=False)[0:2]
 
388
        finally:
 
389
            versionedfile.clear_cache()
 
390
 
 
391
 
 
392
class RootCommitBuilder(CommitBuilder):
 
393
    """This commitbuilder actually records the root id"""
 
394
    
 
395
    # the root entry gets versioned properly by this builder.
 
396
    _versioned_root = True
 
397
 
 
398
    def _check_root(self, ie, parent_invs, tree):
 
399
        """Helper for record_entry_contents.
 
400
 
 
401
        :param ie: An entry being added.
 
402
        :param parent_invs: The inventories of the parent revisions of the
 
403
            commit.
 
404
        :param tree: The tree that is being committed.
 
405
        """
 
406
        # ie must be root for this builder
 
407
        assert ie.parent_id is None
 
408
 
 
409
 
 
410
######################################################################
 
411
# Repositories
 
412
 
 
413
class Repository(object):
 
414
    """Repository holding history for one or more branches.
 
415
 
 
416
    The repository holds and retrieves historical information including
 
417
    revisions and file history.  It's normally accessed only by the Branch,
 
418
    which views a particular line of development through that history.
 
419
 
 
420
    The Repository builds on top of Stores and a Transport, which respectively 
 
421
    describe the disk data format and the way of accessing the (possibly 
 
422
    remote) disk.
 
423
    """
 
424
 
 
425
    # What class to use for a CommitBuilder. Often its simpler to change this
 
426
    # in a Repository class subclass rather than to override
 
427
    # get_commit_builder.
 
428
    _commit_builder_class = CommitBuilder
 
429
    # The search regex used by xml based repositories to determine what things
 
430
    # where changed in a single commit.
 
431
    _file_ids_altered_regex = lazy_regex.lazy_compile(
 
432
        r'file_id="(?P<file_id>[^"]+)"'
 
433
        r'.* revision="(?P<revision_id>[^"]+)"'
 
434
        )
 
435
 
 
436
    def abort_write_group(self):
 
437
        """Commit the contents accrued within the current write group.
 
438
 
 
439
        :seealso: start_write_group.
 
440
        """
 
441
        if self._write_group is not self.get_transaction():
 
442
            # has an unlock or relock occured ?
 
443
            raise errors.BzrError('mismatched lock context and write group.')
 
444
        self._abort_write_group()
 
445
        self._write_group = None
 
446
 
 
447
    def _abort_write_group(self):
 
448
        """Template method for per-repository write group cleanup.
 
449
        
 
450
        This is called during abort before the write group is considered to be 
 
451
        finished and should cleanup any internal state accrued during the write
 
452
        group. There is no requirement that data handed to the repository be
 
453
        *not* made available - this is not a rollback - but neither should any
 
454
        attempt be made to ensure that data added is fully commited. Abort is
 
455
        invoked when an error has occured so futher disk or network operations
 
456
        may not be possible or may error and if possible should not be
 
457
        attempted.
 
458
        """
 
459
 
 
460
    @needs_write_lock
 
461
    def add_inventory(self, revision_id, inv, parents):
 
462
        """Add the inventory inv to the repository as revision_id.
 
463
        
 
464
        :param parents: The revision ids of the parents that revision_id
 
465
                        is known to have and are in the repository already.
 
466
 
 
467
        returns the sha1 of the serialized inventory.
 
468
        """
 
469
        revision_id = osutils.safe_revision_id(revision_id)
 
470
        _mod_revision.check_not_reserved_id(revision_id)
 
471
        assert inv.revision_id is None or inv.revision_id == revision_id, \
 
472
            "Mismatch between inventory revision" \
 
473
            " id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
 
474
        assert inv.root is not None
 
475
        inv_lines = self._serialise_inventory_to_lines(inv)
 
476
        inv_vf = self.get_inventory_weave()
 
477
        return self._inventory_add_lines(inv_vf, revision_id, parents,
 
478
            inv_lines, check_content=False)
 
479
 
 
480
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines,
 
481
        check_content=True):
 
482
        """Store lines in inv_vf and return the sha1 of the inventory."""
 
483
        final_parents = []
 
484
        for parent in parents:
 
485
            if parent in inv_vf:
 
486
                final_parents.append(parent)
 
487
        return inv_vf.add_lines(revision_id, final_parents, lines,
 
488
            check_content=check_content)[0]
 
489
 
 
490
    @needs_write_lock
 
491
    def add_revision(self, revision_id, rev, inv=None, config=None):
 
492
        """Add rev to the revision store as revision_id.
 
493
 
 
494
        :param revision_id: the revision id to use.
 
495
        :param rev: The revision object.
 
496
        :param inv: The inventory for the revision. if None, it will be looked
 
497
                    up in the inventory storer
 
498
        :param config: If None no digital signature will be created.
 
499
                       If supplied its signature_needed method will be used
 
500
                       to determine if a signature should be made.
 
501
        """
 
502
        revision_id = osutils.safe_revision_id(revision_id)
 
503
        # TODO: jam 20070210 Shouldn't we check rev.revision_id and
 
504
        #       rev.parent_ids?
 
505
        _mod_revision.check_not_reserved_id(revision_id)
 
506
        if config is not None and config.signature_needed():
 
507
            if inv is None:
 
508
                inv = self.get_inventory(revision_id)
 
509
            plaintext = Testament(rev, inv).as_short_text()
 
510
            self.store_revision_signature(
 
511
                gpg.GPGStrategy(config), plaintext, revision_id)
 
512
        if not revision_id in self.get_inventory_weave():
 
513
            if inv is None:
 
514
                raise errors.WeaveRevisionNotPresent(revision_id,
 
515
                                                     self.get_inventory_weave())
 
516
            else:
 
517
                # yes, this is not suitable for adding with ghosts.
 
518
                self.add_inventory(revision_id, inv, rev.parent_ids)
 
519
        self._revision_store.add_revision(rev, self.get_transaction())
 
520
 
 
521
    def _add_revision_text(self, revision_id, text):
 
522
        revision = self._revision_store._serializer.read_revision_from_string(
 
523
            text)
 
524
        self._revision_store._add_revision(revision, StringIO(text),
 
525
                                           self.get_transaction())
 
526
 
 
527
    def all_revision_ids(self):
 
528
        """Returns a list of all the revision ids in the repository. 
 
529
 
 
530
        This is deprecated because code should generally work on the graph
 
531
        reachable from a particular revision, and ignore any other revisions
 
532
        that might be present.  There is no direct replacement method.
 
533
        """
 
534
        if 'evil' in debug.debug_flags:
 
535
            mutter_callsite(2, "all_revision_ids is linear with history.")
 
536
        return self._all_revision_ids()
 
537
 
 
538
    def _all_revision_ids(self):
 
539
        """Returns a list of all the revision ids in the repository. 
 
540
 
 
541
        These are in as much topological order as the underlying store can 
 
542
        present.
 
543
        """
 
544
        raise NotImplementedError(self._all_revision_ids)
 
545
 
 
546
    def break_lock(self):
 
547
        """Break a lock if one is present from another instance.
 
548
 
 
549
        Uses the ui factory to ask for confirmation if the lock may be from
 
550
        an active process.
 
551
        """
 
552
        self.control_files.break_lock()
 
553
 
 
554
    @needs_read_lock
 
555
    def _eliminate_revisions_not_present(self, revision_ids):
 
556
        """Check every revision id in revision_ids to see if we have it.
 
557
 
 
558
        Returns a set of the present revisions.
 
559
        """
 
560
        result = []
 
561
        for id in revision_ids:
 
562
            if self.has_revision(id):
 
563
               result.append(id)
 
564
        return result
 
565
 
 
566
    @staticmethod
 
567
    def create(a_bzrdir):
 
568
        """Construct the current default format repository in a_bzrdir."""
 
569
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
 
570
 
 
571
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
572
        """instantiate a Repository.
 
573
 
 
574
        :param _format: The format of the repository on disk.
 
575
        :param a_bzrdir: The BzrDir of the repository.
 
576
 
 
577
        In the future we will have a single api for all stores for
 
578
        getting file texts, inventories and revisions, then
 
579
        this construct will accept instances of those things.
 
580
        """
 
581
        super(Repository, self).__init__()
 
582
        self._format = _format
 
583
        # the following are part of the public API for Repository:
 
584
        self.bzrdir = a_bzrdir
 
585
        self.control_files = control_files
 
586
        self._revision_store = _revision_store
 
587
        # backwards compatibility
 
588
        self.weave_store = text_store
 
589
        # for tests
 
590
        self._reconcile_does_inventory_gc = True
 
591
        # not right yet - should be more semantically clear ? 
 
592
        # 
 
593
        self.control_store = control_store
 
594
        self.control_weaves = control_store
 
595
        # TODO: make sure to construct the right store classes, etc, depending
 
596
        # on whether escaping is required.
 
597
        self._warn_if_deprecated()
 
598
        self._write_group = None
 
599
 
 
600
    def __repr__(self):
 
601
        return '%s(%r)' % (self.__class__.__name__, 
 
602
                           self.bzrdir.transport.base)
 
603
 
 
604
    def has_same_location(self, other):
 
605
        """Returns a boolean indicating if this repository is at the same
 
606
        location as another repository.
 
607
 
 
608
        This might return False even when two repository objects are accessing
 
609
        the same physical repository via different URLs.
 
610
        """
 
611
        if self.__class__ is not other.__class__:
 
612
            return False
 
613
        return (self.control_files._transport.base ==
 
614
                other.control_files._transport.base)
 
615
 
 
616
    def is_in_write_group(self):
 
617
        """Return True if there is an open write group.
 
618
 
 
619
        :seealso: start_write_group.
 
620
        """
 
621
        return self._write_group is not None
 
622
 
 
623
    def is_locked(self):
 
624
        return self.control_files.is_locked()
 
625
 
 
626
    def lock_write(self, token=None):
 
627
        """Lock this repository for writing.
 
628
 
 
629
        This causes caching within the repository obejct to start accumlating
 
630
        data during reads, and allows a 'write_group' to be obtained. Write
 
631
        groups must be used for actual data insertion.
 
632
        
 
633
        :param token: if this is already locked, then lock_write will fail
 
634
            unless the token matches the existing lock.
 
635
        :returns: a token if this instance supports tokens, otherwise None.
 
636
        :raises TokenLockingNotSupported: when a token is given but this
 
637
            instance doesn't support using token locks.
 
638
        :raises MismatchedToken: if the specified token doesn't match the token
 
639
            of the existing lock.
 
640
        :seealso: start_write_group.
 
641
 
 
642
        A token should be passed in if you know that you have locked the object
 
643
        some other way, and need to synchronise this object's state with that
 
644
        fact.
 
645
 
 
646
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
647
        """
 
648
        result = self.control_files.lock_write(token=token)
 
649
        self._refresh_data()
 
650
        return result
 
651
 
 
652
    def lock_read(self):
 
653
        self.control_files.lock_read()
 
654
        self._refresh_data()
 
655
 
 
656
    def get_physical_lock_status(self):
 
657
        return self.control_files.get_physical_lock_status()
 
658
 
 
659
    def leave_lock_in_place(self):
 
660
        """Tell this repository not to release the physical lock when this
 
661
        object is unlocked.
 
662
        
 
663
        If lock_write doesn't return a token, then this method is not supported.
 
664
        """
 
665
        self.control_files.leave_in_place()
 
666
 
 
667
    def dont_leave_lock_in_place(self):
 
668
        """Tell this repository to release the physical lock when this
 
669
        object is unlocked, even if it didn't originally acquire it.
 
670
 
 
671
        If lock_write doesn't return a token, then this method is not supported.
 
672
        """
 
673
        self.control_files.dont_leave_in_place()
 
674
 
 
675
    @needs_read_lock
 
676
    def gather_stats(self, revid=None, committers=None):
 
677
        """Gather statistics from a revision id.
 
678
 
 
679
        :param revid: The revision id to gather statistics from, if None, then
 
680
            no revision specific statistics are gathered.
 
681
        :param committers: Optional parameter controlling whether to grab
 
682
            a count of committers from the revision specific statistics.
 
683
        :return: A dictionary of statistics. Currently this contains:
 
684
            committers: The number of committers if requested.
 
685
            firstrev: A tuple with timestamp, timezone for the penultimate left
 
686
                most ancestor of revid, if revid is not the NULL_REVISION.
 
687
            latestrev: A tuple with timestamp, timezone for revid, if revid is
 
688
                not the NULL_REVISION.
 
689
            revisions: The total revision count in the repository.
 
690
            size: An estimate disk size of the repository in bytes.
 
691
        """
 
692
        result = {}
 
693
        if revid and committers:
 
694
            result['committers'] = 0
 
695
        if revid and revid != _mod_revision.NULL_REVISION:
 
696
            if committers:
 
697
                all_committers = set()
 
698
            revisions = self.get_ancestry(revid)
 
699
            # pop the leading None
 
700
            revisions.pop(0)
 
701
            first_revision = None
 
702
            if not committers:
 
703
                # ignore the revisions in the middle - just grab first and last
 
704
                revisions = revisions[0], revisions[-1]
 
705
            for revision in self.get_revisions(revisions):
 
706
                if not first_revision:
 
707
                    first_revision = revision
 
708
                if committers:
 
709
                    all_committers.add(revision.committer)
 
710
            last_revision = revision
 
711
            if committers:
 
712
                result['committers'] = len(all_committers)
 
713
            result['firstrev'] = (first_revision.timestamp,
 
714
                first_revision.timezone)
 
715
            result['latestrev'] = (last_revision.timestamp,
 
716
                last_revision.timezone)
 
717
 
 
718
        # now gather global repository information
 
719
        if self.bzrdir.root_transport.listable():
 
720
            c, t = self._revision_store.total_size(self.get_transaction())
 
721
            result['revisions'] = c
 
722
            result['size'] = t
 
723
        return result
 
724
 
 
725
    @needs_read_lock
 
726
    def missing_revision_ids(self, other, revision_id=None):
 
727
        """Return the revision ids that other has that this does not.
 
728
        
 
729
        These are returned in topological order.
 
730
 
 
731
        revision_id: only return revision ids included by revision_id.
 
732
        """
 
733
        revision_id = osutils.safe_revision_id(revision_id)
 
734
        return InterRepository.get(other, self).missing_revision_ids(revision_id)
 
735
 
 
736
    @staticmethod
 
737
    def open(base):
 
738
        """Open the repository rooted at base.
 
739
 
 
740
        For instance, if the repository is at URL/.bzr/repository,
 
741
        Repository.open(URL) -> a Repository instance.
 
742
        """
 
743
        control = bzrdir.BzrDir.open(base)
 
744
        return control.open_repository()
 
745
 
 
746
    def copy_content_into(self, destination, revision_id=None):
 
747
        """Make a complete copy of the content in self into destination.
 
748
        
 
749
        This is a destructive operation! Do not use it on existing 
 
750
        repositories.
 
751
        """
 
752
        revision_id = osutils.safe_revision_id(revision_id)
 
753
        return InterRepository.get(self, destination).copy_content(revision_id)
 
754
 
 
755
    def commit_write_group(self):
 
756
        """Commit the contents accrued within the current write group.
 
757
 
 
758
        :seealso: start_write_group.
 
759
        """
 
760
        if self._write_group is not self.get_transaction():
 
761
            # has an unlock or relock occured ?
 
762
            raise errors.BzrError('mismatched lock context and write group.')
 
763
        self._commit_write_group()
 
764
        self._write_group = None
 
765
 
 
766
    def _commit_write_group(self):
 
767
        """Template method for per-repository write group cleanup.
 
768
        
 
769
        This is called before the write group is considered to be 
 
770
        finished and should ensure that all data handed to the repository
 
771
        for writing during the write group is safely committed (to the 
 
772
        extent possible considering file system caching etc).
 
773
        """
 
774
 
 
775
    def fetch(self, source, revision_id=None, pb=None):
 
776
        """Fetch the content required to construct revision_id from source.
 
777
 
 
778
        If revision_id is None all content is copied.
 
779
        """
 
780
        revision_id = osutils.safe_revision_id(revision_id)
 
781
        inter = InterRepository.get(source, self)
 
782
        try:
 
783
            return inter.fetch(revision_id=revision_id, pb=pb)
 
784
        except NotImplementedError:
 
785
            raise errors.IncompatibleRepositories(source, self)
 
786
 
 
787
    def create_bundle(self, target, base, fileobj, format=None):
 
788
        return serializer.write_bundle(self, target, base, fileobj, format)
 
789
 
 
790
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
791
                           timezone=None, committer=None, revprops=None,
 
792
                           revision_id=None):
 
793
        """Obtain a CommitBuilder for this repository.
 
794
        
 
795
        :param branch: Branch to commit to.
 
796
        :param parents: Revision ids of the parents of the new revision.
 
797
        :param config: Configuration to use.
 
798
        :param timestamp: Optional timestamp recorded for commit.
 
799
        :param timezone: Optional timezone for timestamp.
 
800
        :param committer: Optional committer to set for commit.
 
801
        :param revprops: Optional dictionary of revision properties.
 
802
        :param revision_id: Optional revision id.
 
803
        """
 
804
        revision_id = osutils.safe_revision_id(revision_id)
 
805
        result = self._commit_builder_class(self, parents, config,
 
806
            timestamp, timezone, committer, revprops, revision_id)
 
807
        self.start_write_group()
 
808
        return result
 
809
 
 
810
    def unlock(self):
 
811
        if (self.control_files._lock_count == 1 and
 
812
            self.control_files._lock_mode == 'w'):
 
813
            if self._write_group is not None:
 
814
                raise errors.BzrError(
 
815
                    'Must end write groups before releasing write locks.')
 
816
        self.control_files.unlock()
 
817
 
 
818
    @needs_read_lock
 
819
    def clone(self, a_bzrdir, revision_id=None):
 
820
        """Clone this repository into a_bzrdir using the current format.
 
821
 
 
822
        Currently no check is made that the format of this repository and
 
823
        the bzrdir format are compatible. FIXME RBC 20060201.
 
824
 
 
825
        :return: The newly created destination repository.
 
826
        """
 
827
        # TODO: deprecate after 0.16; cloning this with all its settings is
 
828
        # probably not very useful -- mbp 20070423
 
829
        dest_repo = self._create_sprouting_repo(a_bzrdir, shared=self.is_shared())
 
830
        self.copy_content_into(dest_repo, revision_id)
 
831
        return dest_repo
 
832
 
 
833
    def start_write_group(self):
 
834
        """Start a write group in the repository.
 
835
 
 
836
        Write groups are used by repositories which do not have a 1:1 mapping
 
837
        between file ids and backend store to manage the insertion of data from
 
838
        both fetch and commit operations.
 
839
 
 
840
        A write lock is required around the start_write_group/commit_write_group
 
841
        for the support of lock-requiring repository formats.
 
842
 
 
843
        One can only insert data into a repository inside a write group.
 
844
 
 
845
        :return: None.
 
846
        """
 
847
        if not self.is_locked() or self.control_files._lock_mode != 'w':
 
848
            raise errors.NotWriteLocked(self)
 
849
        if self._write_group:
 
850
            raise errors.BzrError('already in a write group')
 
851
        self._start_write_group()
 
852
        # so we can detect unlock/relock - the write group is now entered.
 
853
        self._write_group = self.get_transaction()
 
854
 
 
855
    def _start_write_group(self):
 
856
        """Template method for per-repository write group startup.
 
857
        
 
858
        This is called before the write group is considered to be 
 
859
        entered.
 
860
        """
 
861
 
 
862
    @needs_read_lock
 
863
    def sprout(self, to_bzrdir, revision_id=None):
 
864
        """Create a descendent repository for new development.
 
865
 
 
866
        Unlike clone, this does not copy the settings of the repository.
 
867
        """
 
868
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
 
869
        dest_repo.fetch(self, revision_id=revision_id)
 
870
        return dest_repo
 
871
 
 
872
    def _create_sprouting_repo(self, a_bzrdir, shared):
 
873
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
 
874
            # use target default format.
 
875
            dest_repo = a_bzrdir.create_repository()
 
876
        else:
 
877
            # Most control formats need the repository to be specifically
 
878
            # created, but on some old all-in-one formats it's not needed
 
879
            try:
 
880
                dest_repo = self._format.initialize(a_bzrdir, shared=shared)
 
881
            except errors.UninitializableFormat:
 
882
                dest_repo = a_bzrdir.open_repository()
 
883
        return dest_repo
 
884
 
 
885
    @needs_read_lock
 
886
    def has_revision(self, revision_id):
 
887
        """True if this repository has a copy of the revision."""
 
888
        if 'evil' in debug.debug_flags:
 
889
            mutter_callsite(2, "has_revision is a LBYL symptom.")
 
890
        revision_id = osutils.safe_revision_id(revision_id)
 
891
        return self._revision_store.has_revision_id(revision_id,
 
892
                                                    self.get_transaction())
 
893
 
 
894
    @needs_read_lock
 
895
    def get_revision(self, revision_id):
 
896
        """Return the Revision object for a named revision."""
 
897
        return self.get_revisions([revision_id])[0]
 
898
 
 
899
    @needs_read_lock
 
900
    def get_revision_reconcile(self, revision_id):
 
901
        """'reconcile' helper routine that allows access to a revision always.
 
902
        
 
903
        This variant of get_revision does not cross check the weave graph
 
904
        against the revision one as get_revision does: but it should only
 
905
        be used by reconcile, or reconcile-alike commands that are correcting
 
906
        or testing the revision graph.
 
907
        """
 
908
        return self._get_revisions([revision_id])[0]
 
909
 
 
910
    @needs_read_lock
 
911
    def get_revisions(self, revision_ids):
 
912
        """Get many revisions at once."""
 
913
        return self._get_revisions(revision_ids)
 
914
 
 
915
    @needs_read_lock
 
916
    def _get_revisions(self, revision_ids):
 
917
        """Core work logic to get many revisions without sanity checks."""
 
918
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
 
919
        for rev_id in revision_ids:
 
920
            if not rev_id or not isinstance(rev_id, basestring):
 
921
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
 
922
        revs = self._revision_store.get_revisions(revision_ids,
 
923
                                                  self.get_transaction())
 
924
        for rev in revs:
 
925
            assert not isinstance(rev.revision_id, unicode)
 
926
            for parent_id in rev.parent_ids:
 
927
                assert not isinstance(parent_id, unicode)
 
928
        return revs
 
929
 
 
930
    @needs_read_lock
 
931
    def get_revision_xml(self, revision_id):
 
932
        # TODO: jam 20070210 This shouldn't be necessary since get_revision
 
933
        #       would have already do it.
 
934
        # TODO: jam 20070210 Just use _serializer.write_revision_to_string()
 
935
        revision_id = osutils.safe_revision_id(revision_id)
 
936
        rev = self.get_revision(revision_id)
 
937
        rev_tmp = StringIO()
 
938
        # the current serializer..
 
939
        self._revision_store._serializer.write_revision(rev, rev_tmp)
 
940
        rev_tmp.seek(0)
 
941
        return rev_tmp.getvalue()
 
942
 
 
943
    @needs_read_lock
 
944
    def get_deltas_for_revisions(self, revisions):
 
945
        """Produce a generator of revision deltas.
 
946
        
 
947
        Note that the input is a sequence of REVISIONS, not revision_ids.
 
948
        Trees will be held in memory until the generator exits.
 
949
        Each delta is relative to the revision's lefthand predecessor.
 
950
        """
 
951
        required_trees = set()
 
952
        for revision in revisions:
 
953
            required_trees.add(revision.revision_id)
 
954
            required_trees.update(revision.parent_ids[:1])
 
955
        trees = dict((t.get_revision_id(), t) for 
 
956
                     t in self.revision_trees(required_trees))
 
957
        for revision in revisions:
 
958
            if not revision.parent_ids:
 
959
                old_tree = self.revision_tree(None)
 
960
            else:
 
961
                old_tree = trees[revision.parent_ids[0]]
 
962
            yield trees[revision.revision_id].changes_from(old_tree)
 
963
 
 
964
    @needs_read_lock
 
965
    def get_revision_delta(self, revision_id):
 
966
        """Return the delta for one revision.
 
967
 
 
968
        The delta is relative to the left-hand predecessor of the
 
969
        revision.
 
970
        """
 
971
        r = self.get_revision(revision_id)
 
972
        return list(self.get_deltas_for_revisions([r]))[0]
 
973
 
 
974
    @needs_write_lock
 
975
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
976
        revision_id = osutils.safe_revision_id(revision_id)
 
977
        signature = gpg_strategy.sign(plaintext)
 
978
        self._revision_store.add_revision_signature_text(revision_id,
 
979
                                                         signature,
 
980
                                                         self.get_transaction())
 
981
 
 
982
    def fileids_altered_by_revision_ids(self, revision_ids):
 
983
        """Find the file ids and versions affected by revisions.
 
984
 
 
985
        :param revisions: an iterable containing revision ids.
 
986
        :return: a dictionary mapping altered file-ids to an iterable of
 
987
        revision_ids. Each altered file-ids has the exact revision_ids that
 
988
        altered it listed explicitly.
 
989
        """
 
990
        assert self._serializer.support_altered_by_hack, \
 
991
            ("fileids_altered_by_revision_ids only supported for branches " 
 
992
             "which store inventory as unnested xml, not on %r" % self)
 
993
        selected_revision_ids = set(osutils.safe_revision_id(r)
 
994
                                    for r in revision_ids)
 
995
        w = self.get_inventory_weave()
 
996
        result = {}
 
997
 
 
998
        # this code needs to read every new line in every inventory for the
 
999
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
 
1000
        # not present in one of those inventories is unnecessary but not 
 
1001
        # harmful because we are filtering by the revision id marker in the
 
1002
        # inventory lines : we only select file ids altered in one of those  
 
1003
        # revisions. We don't need to see all lines in the inventory because
 
1004
        # only those added in an inventory in rev X can contain a revision=X
 
1005
        # line.
 
1006
        unescape_revid_cache = {}
 
1007
        unescape_fileid_cache = {}
 
1008
 
 
1009
        # jam 20061218 In a big fetch, this handles hundreds of thousands
 
1010
        # of lines, so it has had a lot of inlining and optimizing done.
 
1011
        # Sorry that it is a little bit messy.
 
1012
        # Move several functions to be local variables, since this is a long
 
1013
        # running loop.
 
1014
        search = self._file_ids_altered_regex.search
 
1015
        unescape = _unescape_xml
 
1016
        setdefault = result.setdefault
 
1017
        pb = ui.ui_factory.nested_progress_bar()
 
1018
        try:
 
1019
            for line in w.iter_lines_added_or_present_in_versions(
 
1020
                                        selected_revision_ids, pb=pb):
 
1021
                match = search(line)
 
1022
                if match is None:
 
1023
                    continue
 
1024
                # One call to match.group() returning multiple items is quite a
 
1025
                # bit faster than 2 calls to match.group() each returning 1
 
1026
                file_id, revision_id = match.group('file_id', 'revision_id')
 
1027
 
 
1028
                # Inlining the cache lookups helps a lot when you make 170,000
 
1029
                # lines and 350k ids, versus 8.4 unique ids.
 
1030
                # Using a cache helps in 2 ways:
 
1031
                #   1) Avoids unnecessary decoding calls
 
1032
                #   2) Re-uses cached strings, which helps in future set and
 
1033
                #      equality checks.
 
1034
                # (2) is enough that removing encoding entirely along with
 
1035
                # the cache (so we are using plain strings) results in no
 
1036
                # performance improvement.
 
1037
                try:
 
1038
                    revision_id = unescape_revid_cache[revision_id]
 
1039
                except KeyError:
 
1040
                    unescaped = unescape(revision_id)
 
1041
                    unescape_revid_cache[revision_id] = unescaped
 
1042
                    revision_id = unescaped
 
1043
 
 
1044
                if revision_id in selected_revision_ids:
 
1045
                    try:
 
1046
                        file_id = unescape_fileid_cache[file_id]
 
1047
                    except KeyError:
 
1048
                        unescaped = unescape(file_id)
 
1049
                        unescape_fileid_cache[file_id] = unescaped
 
1050
                        file_id = unescaped
 
1051
                    setdefault(file_id, set()).add(revision_id)
 
1052
        finally:
 
1053
            pb.finished()
 
1054
        return result
 
1055
 
 
1056
    def iter_files_bytes(self, desired_files):
 
1057
        """Iterate through file versions.
 
1058
 
 
1059
        Files will not necessarily be returned in the order they occur in
 
1060
        desired_files.  No specific order is guaranteed.
 
1061
 
 
1062
        Yields pairs of identifier, bytes_iterator.  identifier is an opaque
 
1063
        value supplied by the caller as part of desired_files.  It should
 
1064
        uniquely identify the file version in the caller's context.  (Examples:
 
1065
        an index number or a TreeTransform trans_id.)
 
1066
 
 
1067
        bytes_iterator is an iterable of bytestrings for the file.  The
 
1068
        kind of iterable and length of the bytestrings are unspecified, but for
 
1069
        this implementation, it is a list of lines produced by
 
1070
        VersionedFile.get_lines().
 
1071
 
 
1072
        :param desired_files: a list of (file_id, revision_id, identifier)
 
1073
            triples
 
1074
        """
 
1075
        transaction = self.get_transaction()
 
1076
        for file_id, revision_id, callable_data in desired_files:
 
1077
            try:
 
1078
                weave = self.weave_store.get_weave(file_id, transaction)
 
1079
            except errors.NoSuchFile:
 
1080
                raise errors.NoSuchIdInRepository(self, file_id)
 
1081
            yield callable_data, weave.get_lines(revision_id)
 
1082
 
 
1083
    def item_keys_introduced_by(self, revision_ids, _files_pb=None):
 
1084
        """Get an iterable listing the keys of all the data introduced by a set
 
1085
        of revision IDs.
 
1086
 
 
1087
        The keys will be ordered so that the corresponding items can be safely
 
1088
        fetched and inserted in that order.
 
1089
 
 
1090
        :returns: An iterable producing tuples of (knit-kind, file-id,
 
1091
            versions).  knit-kind is one of 'file', 'inventory', 'signatures',
 
1092
            'revisions'.  file-id is None unless knit-kind is 'file'.
 
1093
        """
 
1094
        # XXX: it's a bit weird to control the inventory weave caching in this
 
1095
        # generator.  Ideally the caching would be done in fetch.py I think.  Or
 
1096
        # maybe this generator should explicitly have the contract that it
 
1097
        # should not be iterated until the previously yielded item has been
 
1098
        # processed?
 
1099
        inv_w = self.get_inventory_weave()
 
1100
        inv_w.enable_cache()
 
1101
 
 
1102
        # file ids that changed
 
1103
        file_ids = self.fileids_altered_by_revision_ids(revision_ids)
 
1104
        count = 0
 
1105
        num_file_ids = len(file_ids)
 
1106
        for file_id, altered_versions in file_ids.iteritems():
 
1107
            if _files_pb is not None:
 
1108
                _files_pb.update("fetch texts", count, num_file_ids)
 
1109
            count += 1
 
1110
            yield ("file", file_id, altered_versions)
 
1111
        # We're done with the files_pb.  Note that it finished by the caller,
 
1112
        # just as it was created by the caller.
 
1113
        del _files_pb
 
1114
 
 
1115
        # inventory
 
1116
        yield ("inventory", None, revision_ids)
 
1117
        inv_w.clear_cache()
 
1118
 
 
1119
        # signatures
 
1120
        revisions_with_signatures = set()
 
1121
        for rev_id in revision_ids:
 
1122
            try:
 
1123
                self.get_signature_text(rev_id)
 
1124
            except errors.NoSuchRevision:
 
1125
                # not signed.
 
1126
                pass
 
1127
            else:
 
1128
                revisions_with_signatures.add(rev_id)
 
1129
        yield ("signatures", None, revisions_with_signatures)
 
1130
 
 
1131
        # revisions
 
1132
        yield ("revisions", None, revision_ids)
 
1133
 
 
1134
    @needs_read_lock
 
1135
    def get_inventory_weave(self):
 
1136
        return self.control_weaves.get_weave('inventory',
 
1137
            self.get_transaction())
 
1138
 
 
1139
    @needs_read_lock
 
1140
    def get_inventory(self, revision_id):
 
1141
        """Get Inventory object by hash."""
 
1142
        # TODO: jam 20070210 Technically we don't need to sanitize, since all
 
1143
        #       called functions must sanitize.
 
1144
        revision_id = osutils.safe_revision_id(revision_id)
 
1145
        return self.deserialise_inventory(
 
1146
            revision_id, self.get_inventory_xml(revision_id))
 
1147
 
 
1148
    def deserialise_inventory(self, revision_id, xml):
 
1149
        """Transform the xml into an inventory object. 
 
1150
 
 
1151
        :param revision_id: The expected revision id of the inventory.
 
1152
        :param xml: A serialised inventory.
 
1153
        """
 
1154
        revision_id = osutils.safe_revision_id(revision_id)
 
1155
        result = self._serializer.read_inventory_from_string(xml)
 
1156
        result.root.revision = revision_id
 
1157
        return result
 
1158
 
 
1159
    def serialise_inventory(self, inv):
 
1160
        return self._serializer.write_inventory_to_string(inv)
 
1161
 
 
1162
    def _serialise_inventory_to_lines(self, inv):
 
1163
        return self._serializer.write_inventory_to_lines(inv)
 
1164
 
 
1165
    def get_serializer_format(self):
 
1166
        return self._serializer.format_num
 
1167
 
 
1168
    @needs_read_lock
 
1169
    def get_inventory_xml(self, revision_id):
 
1170
        """Get inventory XML as a file object."""
 
1171
        revision_id = osutils.safe_revision_id(revision_id)
 
1172
        try:
 
1173
            assert isinstance(revision_id, str), type(revision_id)
 
1174
            iw = self.get_inventory_weave()
 
1175
            return iw.get_text(revision_id)
 
1176
        except IndexError:
 
1177
            raise errors.HistoryMissing(self, 'inventory', revision_id)
 
1178
 
 
1179
    @needs_read_lock
 
1180
    def get_inventory_sha1(self, revision_id):
 
1181
        """Return the sha1 hash of the inventory entry
 
1182
        """
 
1183
        # TODO: jam 20070210 Shouldn't this be deprecated / removed?
 
1184
        revision_id = osutils.safe_revision_id(revision_id)
 
1185
        return self.get_revision(revision_id).inventory_sha1
 
1186
 
 
1187
    @needs_read_lock
 
1188
    def get_revision_graph(self, revision_id=None):
 
1189
        """Return a dictionary containing the revision graph.
 
1190
 
 
1191
        NB: This method should not be used as it accesses the entire graph all
 
1192
        at once, which is much more data than most operations should require.
 
1193
 
 
1194
        :param revision_id: The revision_id to get a graph from. If None, then
 
1195
        the entire revision graph is returned. This is a deprecated mode of
 
1196
        operation and will be removed in the future.
 
1197
        :return: a dictionary of revision_id->revision_parents_list.
 
1198
        """
 
1199
        raise NotImplementedError(self.get_revision_graph)
 
1200
 
 
1201
    @needs_read_lock
 
1202
    def get_revision_graph_with_ghosts(self, revision_ids=None):
 
1203
        """Return a graph of the revisions with ghosts marked as applicable.
 
1204
 
 
1205
        :param revision_ids: an iterable of revisions to graph or None for all.
 
1206
        :return: a Graph object with the graph reachable from revision_ids.
 
1207
        """
 
1208
        if 'evil' in debug.debug_flags:
 
1209
            mutter_callsite(2,
 
1210
                "get_revision_graph_with_ghosts scales with size of history.")
 
1211
        result = deprecated_graph.Graph()
 
1212
        if not revision_ids:
 
1213
            pending = set(self.all_revision_ids())
 
1214
            required = set([])
 
1215
        else:
 
1216
            pending = set(osutils.safe_revision_id(r) for r in revision_ids)
 
1217
            # special case NULL_REVISION
 
1218
            if _mod_revision.NULL_REVISION in pending:
 
1219
                pending.remove(_mod_revision.NULL_REVISION)
 
1220
            required = set(pending)
 
1221
        done = set([])
 
1222
        while len(pending):
 
1223
            revision_id = pending.pop()
 
1224
            try:
 
1225
                rev = self.get_revision(revision_id)
 
1226
            except errors.NoSuchRevision:
 
1227
                if revision_id in required:
 
1228
                    raise
 
1229
                # a ghost
 
1230
                result.add_ghost(revision_id)
 
1231
                continue
 
1232
            for parent_id in rev.parent_ids:
 
1233
                # is this queued or done ?
 
1234
                if (parent_id not in pending and
 
1235
                    parent_id not in done):
 
1236
                    # no, queue it.
 
1237
                    pending.add(parent_id)
 
1238
            result.add_node(revision_id, rev.parent_ids)
 
1239
            done.add(revision_id)
 
1240
        return result
 
1241
 
 
1242
    def _get_history_vf(self):
 
1243
        """Get a versionedfile whose history graph reflects all revisions.
 
1244
 
 
1245
        For weave repositories, this is the inventory weave.
 
1246
        """
 
1247
        return self.get_inventory_weave()
 
1248
 
 
1249
    def iter_reverse_revision_history(self, revision_id):
 
1250
        """Iterate backwards through revision ids in the lefthand history
 
1251
 
 
1252
        :param revision_id: The revision id to start with.  All its lefthand
 
1253
            ancestors will be traversed.
 
1254
        """
 
1255
        revision_id = osutils.safe_revision_id(revision_id)
 
1256
        if revision_id in (None, _mod_revision.NULL_REVISION):
 
1257
            return
 
1258
        next_id = revision_id
 
1259
        versionedfile = self._get_history_vf()
 
1260
        while True:
 
1261
            yield next_id
 
1262
            parents = versionedfile.get_parents(next_id)
 
1263
            if len(parents) == 0:
 
1264
                return
 
1265
            else:
 
1266
                next_id = parents[0]
 
1267
 
 
1268
    @needs_read_lock
 
1269
    def get_revision_inventory(self, revision_id):
 
1270
        """Return inventory of a past revision."""
 
1271
        # TODO: Unify this with get_inventory()
 
1272
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
1273
        # must be the same as its revision, so this is trivial.
 
1274
        if revision_id is None:
 
1275
            # This does not make sense: if there is no revision,
 
1276
            # then it is the current tree inventory surely ?!
 
1277
            # and thus get_root_id() is something that looks at the last
 
1278
            # commit on the branch, and the get_root_id is an inventory check.
 
1279
            raise NotImplementedError
 
1280
            # return Inventory(self.get_root_id())
 
1281
        else:
 
1282
            return self.get_inventory(revision_id)
 
1283
 
 
1284
    @needs_read_lock
 
1285
    def is_shared(self):
 
1286
        """Return True if this repository is flagged as a shared repository."""
 
1287
        raise NotImplementedError(self.is_shared)
 
1288
 
 
1289
    @needs_write_lock
 
1290
    def reconcile(self, other=None, thorough=False):
 
1291
        """Reconcile this repository."""
 
1292
        from bzrlib.reconcile import RepoReconciler
 
1293
        reconciler = RepoReconciler(self, thorough=thorough)
 
1294
        reconciler.reconcile()
 
1295
        return reconciler
 
1296
 
 
1297
    def _refresh_data(self):
 
1298
        """Helper called from lock_* to ensure coherency with disk.
 
1299
 
 
1300
        The default implementation does nothing; it is however possible
 
1301
        for repositories to maintain loaded indices across multiple locks
 
1302
        by checking inside their implementation of this method to see
 
1303
        whether their indices are still valid. This depends of course on
 
1304
        the disk format being validatable in this manner.
 
1305
        """
 
1306
 
 
1307
    @needs_read_lock
 
1308
    def revision_tree(self, revision_id):
 
1309
        """Return Tree for a revision on this branch.
 
1310
 
 
1311
        `revision_id` may be None for the empty tree revision.
 
1312
        """
 
1313
        # TODO: refactor this to use an existing revision object
 
1314
        # so we don't need to read it in twice.
 
1315
        if revision_id is None or revision_id == _mod_revision.NULL_REVISION:
 
1316
            return RevisionTree(self, Inventory(root_id=None), 
 
1317
                                _mod_revision.NULL_REVISION)
 
1318
        else:
 
1319
            revision_id = osutils.safe_revision_id(revision_id)
 
1320
            inv = self.get_revision_inventory(revision_id)
 
1321
            return RevisionTree(self, inv, revision_id)
 
1322
 
 
1323
    @needs_read_lock
 
1324
    def revision_trees(self, revision_ids):
 
1325
        """Return Tree for a revision on this branch.
 
1326
 
 
1327
        `revision_id` may not be None or 'null:'"""
 
1328
        assert None not in revision_ids
 
1329
        assert _mod_revision.NULL_REVISION not in revision_ids
 
1330
        texts = self.get_inventory_weave().get_texts(revision_ids)
 
1331
        for text, revision_id in zip(texts, revision_ids):
 
1332
            inv = self.deserialise_inventory(revision_id, text)
 
1333
            yield RevisionTree(self, inv, revision_id)
 
1334
 
 
1335
    @needs_read_lock
 
1336
    def get_ancestry(self, revision_id, topo_sorted=True):
 
1337
        """Return a list of revision-ids integrated by a revision.
 
1338
 
 
1339
        The first element of the list is always None, indicating the origin 
 
1340
        revision.  This might change when we have history horizons, or 
 
1341
        perhaps we should have a new API.
 
1342
        
 
1343
        This is topologically sorted.
 
1344
        """
 
1345
        if _mod_revision.is_null(revision_id):
 
1346
            return [None]
 
1347
        revision_id = osutils.safe_revision_id(revision_id)
 
1348
        if not self.has_revision(revision_id):
 
1349
            raise errors.NoSuchRevision(self, revision_id)
 
1350
        w = self.get_inventory_weave()
 
1351
        candidates = w.get_ancestry(revision_id, topo_sorted)
 
1352
        return [None] + candidates # self._eliminate_revisions_not_present(candidates)
 
1353
 
 
1354
    def pack(self):
 
1355
        """Compress the data within the repository.
 
1356
 
 
1357
        This operation only makes sense for some repository types. For other
 
1358
        types it should be a no-op that just returns.
 
1359
 
 
1360
        This stub method does not require a lock, but subclasses should use
 
1361
        @needs_write_lock as this is a long running call its reasonable to 
 
1362
        implicitly lock for the user.
 
1363
        """
 
1364
 
 
1365
    @needs_read_lock
 
1366
    def print_file(self, file, revision_id):
 
1367
        """Print `file` to stdout.
 
1368
        
 
1369
        FIXME RBC 20060125 as John Meinel points out this is a bad api
 
1370
        - it writes to stdout, it assumes that that is valid etc. Fix
 
1371
        by creating a new more flexible convenience function.
 
1372
        """
 
1373
        revision_id = osutils.safe_revision_id(revision_id)
 
1374
        tree = self.revision_tree(revision_id)
 
1375
        # use inventory as it was in that revision
 
1376
        file_id = tree.inventory.path2id(file)
 
1377
        if not file_id:
 
1378
            # TODO: jam 20060427 Write a test for this code path
 
1379
            #       it had a bug in it, and was raising the wrong
 
1380
            #       exception.
 
1381
            raise errors.BzrError("%r is not present in revision %s" % (file, revision_id))
 
1382
        tree.print_file(file_id)
 
1383
 
 
1384
    def get_transaction(self):
 
1385
        return self.control_files.get_transaction()
 
1386
 
 
1387
    def revision_parents(self, revision_id):
 
1388
        revision_id = osutils.safe_revision_id(revision_id)
 
1389
        return self.get_inventory_weave().parent_names(revision_id)
 
1390
 
 
1391
    def get_parents(self, revision_ids):
 
1392
        """See StackedParentsProvider.get_parents"""
 
1393
        parents_list = []
 
1394
        for revision_id in revision_ids:
 
1395
            if revision_id == _mod_revision.NULL_REVISION:
 
1396
                parents = []
 
1397
            else:
 
1398
                try:
 
1399
                    parents = self.get_revision(revision_id).parent_ids
 
1400
                except errors.NoSuchRevision:
 
1401
                    parents = None
 
1402
                else:
 
1403
                    if len(parents) == 0:
 
1404
                        parents = [_mod_revision.NULL_REVISION]
 
1405
            parents_list.append(parents)
 
1406
        return parents_list
 
1407
 
 
1408
    def _make_parents_provider(self):
 
1409
        return self
 
1410
 
 
1411
    def get_graph(self, other_repository=None):
 
1412
        """Return the graph walker for this repository format"""
 
1413
        parents_provider = self._make_parents_provider()
 
1414
        if (other_repository is not None and
 
1415
            other_repository.bzrdir.transport.base !=
 
1416
            self.bzrdir.transport.base):
 
1417
            parents_provider = graph._StackedParentsProvider(
 
1418
                [parents_provider, other_repository._make_parents_provider()])
 
1419
        return graph.Graph(parents_provider)
 
1420
 
 
1421
    @needs_write_lock
 
1422
    def set_make_working_trees(self, new_value):
 
1423
        """Set the policy flag for making working trees when creating branches.
 
1424
 
 
1425
        This only applies to branches that use this repository.
 
1426
 
 
1427
        The default is 'True'.
 
1428
        :param new_value: True to restore the default, False to disable making
 
1429
                          working trees.
 
1430
        """
 
1431
        raise NotImplementedError(self.set_make_working_trees)
 
1432
    
 
1433
    def make_working_trees(self):
 
1434
        """Returns the policy for making working trees on new branches."""
 
1435
        raise NotImplementedError(self.make_working_trees)
 
1436
 
 
1437
    @needs_write_lock
 
1438
    def sign_revision(self, revision_id, gpg_strategy):
 
1439
        revision_id = osutils.safe_revision_id(revision_id)
 
1440
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
1441
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
1442
 
 
1443
    @needs_read_lock
 
1444
    def has_signature_for_revision_id(self, revision_id):
 
1445
        """Query for a revision signature for revision_id in the repository."""
 
1446
        revision_id = osutils.safe_revision_id(revision_id)
 
1447
        return self._revision_store.has_signature(revision_id,
 
1448
                                                  self.get_transaction())
 
1449
 
 
1450
    @needs_read_lock
 
1451
    def get_signature_text(self, revision_id):
 
1452
        """Return the text for a signature."""
 
1453
        revision_id = osutils.safe_revision_id(revision_id)
 
1454
        return self._revision_store.get_signature_text(revision_id,
 
1455
                                                       self.get_transaction())
 
1456
 
 
1457
    @needs_read_lock
 
1458
    def check(self, revision_ids):
 
1459
        """Check consistency of all history of given revision_ids.
 
1460
 
 
1461
        Different repository implementations should override _check().
 
1462
 
 
1463
        :param revision_ids: A non-empty list of revision_ids whose ancestry
 
1464
             will be checked.  Typically the last revision_id of a branch.
 
1465
        """
 
1466
        if not revision_ids:
 
1467
            raise ValueError("revision_ids must be non-empty in %s.check" 
 
1468
                    % (self,))
 
1469
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
 
1470
        return self._check(revision_ids)
 
1471
 
 
1472
    def _check(self, revision_ids):
 
1473
        result = check.Check(self)
 
1474
        result.check()
 
1475
        return result
 
1476
 
 
1477
    def _warn_if_deprecated(self):
 
1478
        global _deprecation_warning_done
 
1479
        if _deprecation_warning_done:
 
1480
            return
 
1481
        _deprecation_warning_done = True
 
1482
        warning("Format %s for %s is deprecated - please use 'bzr upgrade' to get better performance"
 
1483
                % (self._format, self.bzrdir.transport.base))
 
1484
 
 
1485
    def supports_rich_root(self):
 
1486
        return self._format.rich_root_data
 
1487
 
 
1488
    def _check_ascii_revisionid(self, revision_id, method):
 
1489
        """Private helper for ascii-only repositories."""
 
1490
        # weave repositories refuse to store revisionids that are non-ascii.
 
1491
        if revision_id is not None:
 
1492
            # weaves require ascii revision ids.
 
1493
            if isinstance(revision_id, unicode):
 
1494
                try:
 
1495
                    revision_id.encode('ascii')
 
1496
                except UnicodeEncodeError:
 
1497
                    raise errors.NonAsciiRevisionId(method, self)
 
1498
            else:
 
1499
                try:
 
1500
                    revision_id.decode('ascii')
 
1501
                except UnicodeDecodeError:
 
1502
                    raise errors.NonAsciiRevisionId(method, self)
 
1503
 
 
1504
 
 
1505
 
 
1506
# remove these delegates a while after bzr 0.15
 
1507
def __make_delegated(name, from_module):
 
1508
    def _deprecated_repository_forwarder():
 
1509
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
 
1510
            % (name, from_module),
 
1511
            DeprecationWarning,
 
1512
            stacklevel=2)
 
1513
        m = __import__(from_module, globals(), locals(), [name])
 
1514
        try:
 
1515
            return getattr(m, name)
 
1516
        except AttributeError:
 
1517
            raise AttributeError('module %s has no name %s'
 
1518
                    % (m, name))
 
1519
    globals()[name] = _deprecated_repository_forwarder
 
1520
 
 
1521
for _name in [
 
1522
        'AllInOneRepository',
 
1523
        'WeaveMetaDirRepository',
 
1524
        'PreSplitOutRepositoryFormat',
 
1525
        'RepositoryFormat4',
 
1526
        'RepositoryFormat5',
 
1527
        'RepositoryFormat6',
 
1528
        'RepositoryFormat7',
 
1529
        ]:
 
1530
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
 
1531
 
 
1532
for _name in [
 
1533
        'KnitRepository',
 
1534
        'RepositoryFormatKnit',
 
1535
        'RepositoryFormatKnit1',
 
1536
        ]:
 
1537
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
 
1538
 
 
1539
 
 
1540
def install_revision(repository, rev, revision_tree):
 
1541
    """Install all revision data into a repository."""
 
1542
    present_parents = []
 
1543
    parent_trees = {}
 
1544
    for p_id in rev.parent_ids:
 
1545
        if repository.has_revision(p_id):
 
1546
            present_parents.append(p_id)
 
1547
            parent_trees[p_id] = repository.revision_tree(p_id)
 
1548
        else:
 
1549
            parent_trees[p_id] = repository.revision_tree(None)
 
1550
 
 
1551
    inv = revision_tree.inventory
 
1552
    entries = inv.iter_entries()
 
1553
    # backwards compatibility hack: skip the root id.
 
1554
    if not repository.supports_rich_root():
 
1555
        path, root = entries.next()
 
1556
        if root.revision != rev.revision_id:
 
1557
            raise errors.IncompatibleRevision(repr(repository))
 
1558
    # Add the texts that are not already present
 
1559
    for path, ie in entries:
 
1560
        w = repository.weave_store.get_weave_or_empty(ie.file_id,
 
1561
                repository.get_transaction())
 
1562
        if ie.revision not in w:
 
1563
            text_parents = []
 
1564
            # FIXME: TODO: The following loop *may* be overlapping/duplicate
 
1565
            # with InventoryEntry.find_previous_heads(). if it is, then there
 
1566
            # is a latent bug here where the parents may have ancestors of each
 
1567
            # other. RBC, AB
 
1568
            for revision, tree in parent_trees.iteritems():
 
1569
                if ie.file_id not in tree:
 
1570
                    continue
 
1571
                parent_id = tree.inventory[ie.file_id].revision
 
1572
                if parent_id in text_parents:
 
1573
                    continue
 
1574
                text_parents.append(parent_id)
 
1575
                    
 
1576
            vfile = repository.weave_store.get_weave_or_empty(ie.file_id, 
 
1577
                repository.get_transaction())
 
1578
            lines = revision_tree.get_file(ie.file_id).readlines()
 
1579
            vfile.add_lines(rev.revision_id, text_parents, lines)
 
1580
    try:
 
1581
        # install the inventory
 
1582
        repository.add_inventory(rev.revision_id, inv, present_parents)
 
1583
    except errors.RevisionAlreadyPresent:
 
1584
        pass
 
1585
    repository.add_revision(rev.revision_id, rev, inv)
 
1586
 
 
1587
 
 
1588
class MetaDirRepository(Repository):
 
1589
    """Repositories in the new meta-dir layout."""
 
1590
 
 
1591
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
1592
        super(MetaDirRepository, self).__init__(_format,
 
1593
                                                a_bzrdir,
 
1594
                                                control_files,
 
1595
                                                _revision_store,
 
1596
                                                control_store,
 
1597
                                                text_store)
 
1598
        dir_mode = self.control_files._dir_mode
 
1599
        file_mode = self.control_files._file_mode
 
1600
 
 
1601
    @needs_read_lock
 
1602
    def is_shared(self):
 
1603
        """Return True if this repository is flagged as a shared repository."""
 
1604
        return self.control_files._transport.has('shared-storage')
 
1605
 
 
1606
    @needs_write_lock
 
1607
    def set_make_working_trees(self, new_value):
 
1608
        """Set the policy flag for making working trees when creating branches.
 
1609
 
 
1610
        This only applies to branches that use this repository.
 
1611
 
 
1612
        The default is 'True'.
 
1613
        :param new_value: True to restore the default, False to disable making
 
1614
                          working trees.
 
1615
        """
 
1616
        if new_value:
 
1617
            try:
 
1618
                self.control_files._transport.delete('no-working-trees')
 
1619
            except errors.NoSuchFile:
 
1620
                pass
 
1621
        else:
 
1622
            self.control_files.put_utf8('no-working-trees', '')
 
1623
    
 
1624
    def make_working_trees(self):
 
1625
        """Returns the policy for making working trees on new branches."""
 
1626
        return not self.control_files._transport.has('no-working-trees')
 
1627
 
 
1628
 
 
1629
class RepositoryFormatRegistry(registry.Registry):
 
1630
    """Registry of RepositoryFormats.
 
1631
    """
 
1632
 
 
1633
    def get(self, format_string):
 
1634
        r = registry.Registry.get(self, format_string)
 
1635
        if callable(r):
 
1636
            r = r()
 
1637
        return r
 
1638
    
 
1639
 
 
1640
format_registry = RepositoryFormatRegistry()
 
1641
"""Registry of formats, indexed by their identifying format string.
 
1642
 
 
1643
This can contain either format instances themselves, or classes/factories that
 
1644
can be called to obtain one.
 
1645
"""
 
1646
 
 
1647
 
 
1648
#####################################################################
 
1649
# Repository Formats
 
1650
 
 
1651
class RepositoryFormat(object):
 
1652
    """A repository format.
 
1653
 
 
1654
    Formats provide three things:
 
1655
     * An initialization routine to construct repository data on disk.
 
1656
     * a format string which is used when the BzrDir supports versioned
 
1657
       children.
 
1658
     * an open routine which returns a Repository instance.
 
1659
 
 
1660
    Formats are placed in an dict by their format string for reference 
 
1661
    during opening. These should be subclasses of RepositoryFormat
 
1662
    for consistency.
 
1663
 
 
1664
    Once a format is deprecated, just deprecate the initialize and open
 
1665
    methods on the format class. Do not deprecate the object, as the 
 
1666
    object will be created every system load.
 
1667
 
 
1668
    Common instance attributes:
 
1669
    _matchingbzrdir - the bzrdir format that the repository format was
 
1670
    originally written to work with. This can be used if manually
 
1671
    constructing a bzrdir and repository, or more commonly for test suite
 
1672
    parameterisation.
 
1673
    """
 
1674
 
 
1675
    def __str__(self):
 
1676
        return "<%s>" % self.__class__.__name__
 
1677
 
 
1678
    def __eq__(self, other):
 
1679
        # format objects are generally stateless
 
1680
        return isinstance(other, self.__class__)
 
1681
 
 
1682
    def __ne__(self, other):
 
1683
        return not self == other
 
1684
 
 
1685
    @classmethod
 
1686
    def find_format(klass, a_bzrdir):
 
1687
        """Return the format for the repository object in a_bzrdir.
 
1688
        
 
1689
        This is used by bzr native formats that have a "format" file in
 
1690
        the repository.  Other methods may be used by different types of 
 
1691
        control directory.
 
1692
        """
 
1693
        try:
 
1694
            transport = a_bzrdir.get_repository_transport(None)
 
1695
            format_string = transport.get("format").read()
 
1696
            return format_registry.get(format_string)
 
1697
        except errors.NoSuchFile:
 
1698
            raise errors.NoRepositoryPresent(a_bzrdir)
 
1699
        except KeyError:
 
1700
            raise errors.UnknownFormatError(format=format_string)
 
1701
 
 
1702
    @classmethod
 
1703
    def register_format(klass, format):
 
1704
        format_registry.register(format.get_format_string(), format)
 
1705
 
 
1706
    @classmethod
 
1707
    def unregister_format(klass, format):
 
1708
        format_registry.remove(format.get_format_string())
 
1709
    
 
1710
    @classmethod
 
1711
    def get_default_format(klass):
 
1712
        """Return the current default format."""
 
1713
        from bzrlib import bzrdir
 
1714
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
1715
 
 
1716
    def _get_control_store(self, repo_transport, control_files):
 
1717
        """Return the control store for this repository."""
 
1718
        raise NotImplementedError(self._get_control_store)
 
1719
 
 
1720
    def get_format_string(self):
 
1721
        """Return the ASCII format string that identifies this format.
 
1722
        
 
1723
        Note that in pre format ?? repositories the format string is 
 
1724
        not permitted nor written to disk.
 
1725
        """
 
1726
        raise NotImplementedError(self.get_format_string)
 
1727
 
 
1728
    def get_format_description(self):
 
1729
        """Return the short description for this format."""
 
1730
        raise NotImplementedError(self.get_format_description)
 
1731
 
 
1732
    def _get_revision_store(self, repo_transport, control_files):
 
1733
        """Return the revision store object for this a_bzrdir."""
 
1734
        raise NotImplementedError(self._get_revision_store)
 
1735
 
 
1736
    def _get_text_rev_store(self,
 
1737
                            transport,
 
1738
                            control_files,
 
1739
                            name,
 
1740
                            compressed=True,
 
1741
                            prefixed=False,
 
1742
                            serializer=None):
 
1743
        """Common logic for getting a revision store for a repository.
 
1744
        
 
1745
        see self._get_revision_store for the subclass-overridable method to 
 
1746
        get the store for a repository.
 
1747
        """
 
1748
        from bzrlib.store.revision.text import TextRevisionStore
 
1749
        dir_mode = control_files._dir_mode
 
1750
        file_mode = control_files._file_mode
 
1751
        text_store = TextStore(transport.clone(name),
 
1752
                              prefixed=prefixed,
 
1753
                              compressed=compressed,
 
1754
                              dir_mode=dir_mode,
 
1755
                              file_mode=file_mode)
 
1756
        _revision_store = TextRevisionStore(text_store, serializer)
 
1757
        return _revision_store
 
1758
 
 
1759
    # TODO: this shouldn't be in the base class, it's specific to things that
 
1760
    # use weaves or knits -- mbp 20070207
 
1761
    def _get_versioned_file_store(self,
 
1762
                                  name,
 
1763
                                  transport,
 
1764
                                  control_files,
 
1765
                                  prefixed=True,
 
1766
                                  versionedfile_class=None,
 
1767
                                  versionedfile_kwargs={},
 
1768
                                  escaped=False):
 
1769
        if versionedfile_class is None:
 
1770
            versionedfile_class = self._versionedfile_class
 
1771
        weave_transport = control_files._transport.clone(name)
 
1772
        dir_mode = control_files._dir_mode
 
1773
        file_mode = control_files._file_mode
 
1774
        return VersionedFileStore(weave_transport, prefixed=prefixed,
 
1775
                                  dir_mode=dir_mode,
 
1776
                                  file_mode=file_mode,
 
1777
                                  versionedfile_class=versionedfile_class,
 
1778
                                  versionedfile_kwargs=versionedfile_kwargs,
 
1779
                                  escaped=escaped)
 
1780
 
 
1781
    def initialize(self, a_bzrdir, shared=False):
 
1782
        """Initialize a repository of this format in a_bzrdir.
 
1783
 
 
1784
        :param a_bzrdir: The bzrdir to put the new repository in it.
 
1785
        :param shared: The repository should be initialized as a sharable one.
 
1786
        :returns: The new repository object.
 
1787
        
 
1788
        This may raise UninitializableFormat if shared repository are not
 
1789
        compatible the a_bzrdir.
 
1790
        """
 
1791
        raise NotImplementedError(self.initialize)
 
1792
 
 
1793
    def is_supported(self):
 
1794
        """Is this format supported?
 
1795
 
 
1796
        Supported formats must be initializable and openable.
 
1797
        Unsupported formats may not support initialization or committing or 
 
1798
        some other features depending on the reason for not being supported.
 
1799
        """
 
1800
        return True
 
1801
 
 
1802
    def check_conversion_target(self, target_format):
 
1803
        raise NotImplementedError(self.check_conversion_target)
 
1804
 
 
1805
    def open(self, a_bzrdir, _found=False):
 
1806
        """Return an instance of this format for the bzrdir a_bzrdir.
 
1807
        
 
1808
        _found is a private parameter, do not use it.
 
1809
        """
 
1810
        raise NotImplementedError(self.open)
 
1811
 
 
1812
 
 
1813
class MetaDirRepositoryFormat(RepositoryFormat):
 
1814
    """Common base class for the new repositories using the metadir layout."""
 
1815
 
 
1816
    rich_root_data = False
 
1817
    supports_tree_reference = False
 
1818
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
 
1819
 
 
1820
    def __init__(self):
 
1821
        super(MetaDirRepositoryFormat, self).__init__()
 
1822
 
 
1823
    def _create_control_files(self, a_bzrdir):
 
1824
        """Create the required files and the initial control_files object."""
 
1825
        # FIXME: RBC 20060125 don't peek under the covers
 
1826
        # NB: no need to escape relative paths that are url safe.
 
1827
        repository_transport = a_bzrdir.get_repository_transport(self)
 
1828
        control_files = lockable_files.LockableFiles(repository_transport,
 
1829
                                'lock', lockdir.LockDir)
 
1830
        control_files.create_lock()
 
1831
        return control_files
 
1832
 
 
1833
    def _upload_blank_content(self, a_bzrdir, dirs, files, utf8_files, shared):
 
1834
        """Upload the initial blank content."""
 
1835
        control_files = self._create_control_files(a_bzrdir)
 
1836
        control_files.lock_write()
 
1837
        try:
 
1838
            control_files._transport.mkdir_multi(dirs,
 
1839
                    mode=control_files._dir_mode)
 
1840
            for file, content in files:
 
1841
                control_files.put(file, content)
 
1842
            for file, content in utf8_files:
 
1843
                control_files.put_utf8(file, content)
 
1844
            if shared == True:
 
1845
                control_files.put_utf8('shared-storage', '')
 
1846
        finally:
 
1847
            control_files.unlock()
 
1848
 
 
1849
 
 
1850
# formats which have no format string are not discoverable
 
1851
# and not independently creatable, so are not registered.  They're 
 
1852
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
 
1853
# needed, it's constructed directly by the BzrDir.  Non-native formats where
 
1854
# the repository is not separately opened are similar.
 
1855
 
 
1856
format_registry.register_lazy(
 
1857
    'Bazaar-NG Repository format 7',
 
1858
    'bzrlib.repofmt.weaverepo',
 
1859
    'RepositoryFormat7'
 
1860
    )
 
1861
# KEEP in sync with bzrdir.format_registry default, which controls the overall
 
1862
# default control directory format
 
1863
 
 
1864
format_registry.register_lazy(
 
1865
    'Bazaar-NG Knit Repository Format 1',
 
1866
    'bzrlib.repofmt.knitrepo',
 
1867
    'RepositoryFormatKnit1',
 
1868
    )
 
1869
format_registry.default_key = 'Bazaar-NG Knit Repository Format 1'
 
1870
 
 
1871
format_registry.register_lazy(
 
1872
    'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
 
1873
    'bzrlib.repofmt.knitrepo',
 
1874
    'RepositoryFormatKnit3',
 
1875
    )
 
1876
 
 
1877
 
 
1878
class InterRepository(InterObject):
 
1879
    """This class represents operations taking place between two repositories.
 
1880
 
 
1881
    Its instances have methods like copy_content and fetch, and contain
 
1882
    references to the source and target repositories these operations can be 
 
1883
    carried out on.
 
1884
 
 
1885
    Often we will provide convenience methods on 'repository' which carry out
 
1886
    operations with another repository - they will always forward to
 
1887
    InterRepository.get(other).method_name(parameters).
 
1888
    """
 
1889
 
 
1890
    _optimisers = []
 
1891
    """The available optimised InterRepository types."""
 
1892
 
 
1893
    def copy_content(self, revision_id=None):
 
1894
        raise NotImplementedError(self.copy_content)
 
1895
 
 
1896
    def fetch(self, revision_id=None, pb=None):
 
1897
        """Fetch the content required to construct revision_id.
 
1898
 
 
1899
        The content is copied from self.source to self.target.
 
1900
 
 
1901
        :param revision_id: if None all content is copied, if NULL_REVISION no
 
1902
                            content is copied.
 
1903
        :param pb: optional progress bar to use for progress reports. If not
 
1904
                   provided a default one will be created.
 
1905
 
 
1906
        Returns the copied revision count and the failed revisions in a tuple:
 
1907
        (copied, failures).
 
1908
        """
 
1909
        raise NotImplementedError(self.fetch)
 
1910
   
 
1911
    @needs_read_lock
 
1912
    def missing_revision_ids(self, revision_id=None):
 
1913
        """Return the revision ids that source has that target does not.
 
1914
        
 
1915
        These are returned in topological order.
 
1916
 
 
1917
        :param revision_id: only return revision ids included by this
 
1918
                            revision_id.
 
1919
        """
 
1920
        # generic, possibly worst case, slow code path.
 
1921
        target_ids = set(self.target.all_revision_ids())
 
1922
        if revision_id is not None:
 
1923
            # TODO: jam 20070210 InterRepository is internal enough that it
 
1924
            #       should assume revision_ids are already utf-8
 
1925
            revision_id = osutils.safe_revision_id(revision_id)
 
1926
            source_ids = self.source.get_ancestry(revision_id)
 
1927
            assert source_ids[0] is None
 
1928
            source_ids.pop(0)
 
1929
        else:
 
1930
            source_ids = self.source.all_revision_ids()
 
1931
        result_set = set(source_ids).difference(target_ids)
 
1932
        # this may look like a no-op: its not. It preserves the ordering
 
1933
        # other_ids had while only returning the members from other_ids
 
1934
        # that we've decided we need.
 
1935
        return [rev_id for rev_id in source_ids if rev_id in result_set]
 
1936
 
 
1937
 
 
1938
class InterSameDataRepository(InterRepository):
 
1939
    """Code for converting between repositories that represent the same data.
 
1940
    
 
1941
    Data format and model must match for this to work.
 
1942
    """
 
1943
 
 
1944
    @classmethod
 
1945
    def _get_repo_format_to_test(self):
 
1946
        """Repository format for testing with.
 
1947
        
 
1948
        InterSameData can pull from subtree to subtree and from non-subtree to
 
1949
        non-subtree, so we test this with the richest repository format.
 
1950
        """
 
1951
        from bzrlib.repofmt import knitrepo
 
1952
        return knitrepo.RepositoryFormatKnit3()
 
1953
 
 
1954
    @staticmethod
 
1955
    def is_compatible(source, target):
 
1956
        if source.supports_rich_root() != target.supports_rich_root():
 
1957
            return False
 
1958
        if source._serializer != target._serializer:
 
1959
            return False
 
1960
        return True
 
1961
 
 
1962
    @needs_write_lock
 
1963
    def copy_content(self, revision_id=None):
 
1964
        """Make a complete copy of the content in self into destination.
 
1965
 
 
1966
        This copies both the repository's revision data, and configuration information
 
1967
        such as the make_working_trees setting.
 
1968
        
 
1969
        This is a destructive operation! Do not use it on existing 
 
1970
        repositories.
 
1971
 
 
1972
        :param revision_id: Only copy the content needed to construct
 
1973
                            revision_id and its parents.
 
1974
        """
 
1975
        try:
 
1976
            self.target.set_make_working_trees(self.source.make_working_trees())
 
1977
        except NotImplementedError:
 
1978
            pass
 
1979
        # TODO: jam 20070210 This is fairly internal, so we should probably
 
1980
        #       just assert that revision_id is not unicode.
 
1981
        revision_id = osutils.safe_revision_id(revision_id)
 
1982
        # but don't bother fetching if we have the needed data now.
 
1983
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
 
1984
            self.target.has_revision(revision_id)):
 
1985
            return
 
1986
        self.target.fetch(self.source, revision_id=revision_id)
 
1987
 
 
1988
    @needs_write_lock
 
1989
    def fetch(self, revision_id=None, pb=None):
 
1990
        """See InterRepository.fetch()."""
 
1991
        from bzrlib.fetch import GenericRepoFetcher
 
1992
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
1993
               self.source, self.source._format, self.target, 
 
1994
               self.target._format)
 
1995
        # TODO: jam 20070210 This should be an assert, not a translate
 
1996
        revision_id = osutils.safe_revision_id(revision_id)
 
1997
        f = GenericRepoFetcher(to_repository=self.target,
 
1998
                               from_repository=self.source,
 
1999
                               last_revision=revision_id,
 
2000
                               pb=pb)
 
2001
        return f.count_copied, f.failed_revisions
 
2002
 
 
2003
 
 
2004
class InterWeaveRepo(InterSameDataRepository):
 
2005
    """Optimised code paths between Weave based repositories.
 
2006
    
 
2007
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
 
2008
    implemented lazy inter-object optimisation.
 
2009
    """
 
2010
 
 
2011
    @classmethod
 
2012
    def _get_repo_format_to_test(self):
 
2013
        from bzrlib.repofmt import weaverepo
 
2014
        return weaverepo.RepositoryFormat7()
 
2015
 
 
2016
    @staticmethod
 
2017
    def is_compatible(source, target):
 
2018
        """Be compatible with known Weave formats.
 
2019
        
 
2020
        We don't test for the stores being of specific types because that
 
2021
        could lead to confusing results, and there is no need to be 
 
2022
        overly general.
 
2023
        """
 
2024
        from bzrlib.repofmt.weaverepo import (
 
2025
                RepositoryFormat5,
 
2026
                RepositoryFormat6,
 
2027
                RepositoryFormat7,
 
2028
                )
 
2029
        try:
 
2030
            return (isinstance(source._format, (RepositoryFormat5,
 
2031
                                                RepositoryFormat6,
 
2032
                                                RepositoryFormat7)) and
 
2033
                    isinstance(target._format, (RepositoryFormat5,
 
2034
                                                RepositoryFormat6,
 
2035
                                                RepositoryFormat7)))
 
2036
        except AttributeError:
 
2037
            return False
 
2038
    
 
2039
    @needs_write_lock
 
2040
    def copy_content(self, revision_id=None):
 
2041
        """See InterRepository.copy_content()."""
 
2042
        # weave specific optimised path:
 
2043
        # TODO: jam 20070210 Internal, should be an assert, not translate
 
2044
        revision_id = osutils.safe_revision_id(revision_id)
 
2045
        try:
 
2046
            self.target.set_make_working_trees(self.source.make_working_trees())
 
2047
        except NotImplementedError:
 
2048
            pass
 
2049
        # FIXME do not peek!
 
2050
        if self.source.control_files._transport.listable():
 
2051
            pb = ui.ui_factory.nested_progress_bar()
 
2052
            try:
 
2053
                self.target.weave_store.copy_all_ids(
 
2054
                    self.source.weave_store,
 
2055
                    pb=pb,
 
2056
                    from_transaction=self.source.get_transaction(),
 
2057
                    to_transaction=self.target.get_transaction())
 
2058
                pb.update('copying inventory', 0, 1)
 
2059
                self.target.control_weaves.copy_multi(
 
2060
                    self.source.control_weaves, ['inventory'],
 
2061
                    from_transaction=self.source.get_transaction(),
 
2062
                    to_transaction=self.target.get_transaction())
 
2063
                self.target._revision_store.text_store.copy_all_ids(
 
2064
                    self.source._revision_store.text_store,
 
2065
                    pb=pb)
 
2066
            finally:
 
2067
                pb.finished()
 
2068
        else:
 
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, self.target._format)
 
2077
        # TODO: jam 20070210 This should be an assert, not a translate
 
2078
        revision_id = osutils.safe_revision_id(revision_id)
 
2079
        f = GenericRepoFetcher(to_repository=self.target,
 
2080
                               from_repository=self.source,
 
2081
                               last_revision=revision_id,
 
2082
                               pb=pb)
 
2083
        return f.count_copied, f.failed_revisions
 
2084
 
 
2085
    @needs_read_lock
 
2086
    def missing_revision_ids(self, revision_id=None):
 
2087
        """See InterRepository.missing_revision_ids()."""
 
2088
        # we want all revisions to satisfy revision_id in source.
 
2089
        # but we don't want to stat every file here and there.
 
2090
        # we want then, all revisions other needs to satisfy revision_id 
 
2091
        # checked, but not those that we have locally.
 
2092
        # so the first thing is to get a subset of the revisions to 
 
2093
        # satisfy revision_id in source, and then eliminate those that
 
2094
        # we do already have. 
 
2095
        # this is slow on high latency connection to self, but as as this
 
2096
        # disk format scales terribly for push anyway due to rewriting 
 
2097
        # inventory.weave, this is considered acceptable.
 
2098
        # - RBC 20060209
 
2099
        if revision_id is not None:
 
2100
            source_ids = self.source.get_ancestry(revision_id)
 
2101
            assert source_ids[0] is None
 
2102
            source_ids.pop(0)
 
2103
        else:
 
2104
            source_ids = self.source._all_possible_ids()
 
2105
        source_ids_set = set(source_ids)
 
2106
        # source_ids is the worst possible case we may need to pull.
 
2107
        # now we want to filter source_ids against what we actually
 
2108
        # have in target, but don't try to check for existence where we know
 
2109
        # we do not have a revision as that would be pointless.
 
2110
        target_ids = set(self.target._all_possible_ids())
 
2111
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
2112
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
2113
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
2114
        required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
 
2115
        if revision_id is not None:
 
2116
            # we used get_ancestry to determine source_ids then we are assured all
 
2117
            # revisions referenced are present as they are installed in topological order.
 
2118
            # and the tip revision was validated by get_ancestry.
 
2119
            return required_topo_revisions
 
2120
        else:
 
2121
            # if we just grabbed the possibly available ids, then 
 
2122
            # we only have an estimate of whats available and need to validate
 
2123
            # that against the revision records.
 
2124
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
 
2125
 
 
2126
 
 
2127
class InterKnitRepo(InterSameDataRepository):
 
2128
    """Optimised code paths between Knit based repositories."""
 
2129
 
 
2130
    @classmethod
 
2131
    def _get_repo_format_to_test(self):
 
2132
        from bzrlib.repofmt import knitrepo
 
2133
        return knitrepo.RepositoryFormatKnit1()
 
2134
 
 
2135
    @staticmethod
 
2136
    def is_compatible(source, target):
 
2137
        """Be compatible with known Knit formats.
 
2138
        
 
2139
        We don't test for the stores being of specific types because that
 
2140
        could lead to confusing results, and there is no need to be 
 
2141
        overly general.
 
2142
        """
 
2143
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
 
2144
        try:
 
2145
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
 
2146
                    isinstance(target._format, (RepositoryFormatKnit1)))
 
2147
        except AttributeError:
 
2148
            return False
 
2149
 
 
2150
    @needs_write_lock
 
2151
    def fetch(self, revision_id=None, pb=None):
 
2152
        """See InterRepository.fetch()."""
 
2153
        from bzrlib.fetch import KnitRepoFetcher
 
2154
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2155
               self.source, self.source._format, self.target, self.target._format)
 
2156
        # TODO: jam 20070210 This should be an assert, not a translate
 
2157
        revision_id = osutils.safe_revision_id(revision_id)
 
2158
        f = KnitRepoFetcher(to_repository=self.target,
 
2159
                            from_repository=self.source,
 
2160
                            last_revision=revision_id,
 
2161
                            pb=pb)
 
2162
        return f.count_copied, f.failed_revisions
 
2163
 
 
2164
    @needs_read_lock
 
2165
    def missing_revision_ids(self, revision_id=None):
 
2166
        """See InterRepository.missing_revision_ids()."""
 
2167
        if revision_id is not None:
 
2168
            source_ids = self.source.get_ancestry(revision_id)
 
2169
            assert source_ids[0] is None
 
2170
            source_ids.pop(0)
 
2171
        else:
 
2172
            source_ids = self.source.all_revision_ids()
 
2173
        source_ids_set = set(source_ids)
 
2174
        # source_ids is the worst possible case we may need to pull.
 
2175
        # now we want to filter source_ids against what we actually
 
2176
        # have in target, but don't try to check for existence where we know
 
2177
        # we do not have a revision as that would be pointless.
 
2178
        target_ids = set(self.target.all_revision_ids())
 
2179
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
2180
        actually_present_revisions = set(self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
2181
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
2182
        required_topo_revisions = [rev_id for rev_id in source_ids if rev_id in required_revisions]
 
2183
        if revision_id is not None:
 
2184
            # we used get_ancestry to determine source_ids then we are assured all
 
2185
            # revisions referenced are present as they are installed in topological order.
 
2186
            # and the tip revision was validated by get_ancestry.
 
2187
            return required_topo_revisions
 
2188
        else:
 
2189
            # if we just grabbed the possibly available ids, then 
 
2190
            # we only have an estimate of whats available and need to validate
 
2191
            # that against the revision records.
 
2192
            return self.source._eliminate_revisions_not_present(required_topo_revisions)
 
2193
 
 
2194
 
 
2195
class InterModel1and2(InterRepository):
 
2196
 
 
2197
    @classmethod
 
2198
    def _get_repo_format_to_test(self):
 
2199
        return None
 
2200
 
 
2201
    @staticmethod
 
2202
    def is_compatible(source, target):
 
2203
        if not source.supports_rich_root() and target.supports_rich_root():
 
2204
            return True
 
2205
        else:
 
2206
            return False
 
2207
 
 
2208
    @needs_write_lock
 
2209
    def fetch(self, revision_id=None, pb=None):
 
2210
        """See InterRepository.fetch()."""
 
2211
        from bzrlib.fetch import Model1toKnit2Fetcher
 
2212
        # TODO: jam 20070210 This should be an assert, not a translate
 
2213
        revision_id = osutils.safe_revision_id(revision_id)
 
2214
        f = Model1toKnit2Fetcher(to_repository=self.target,
 
2215
                                 from_repository=self.source,
 
2216
                                 last_revision=revision_id,
 
2217
                                 pb=pb)
 
2218
        return f.count_copied, f.failed_revisions
 
2219
 
 
2220
    @needs_write_lock
 
2221
    def copy_content(self, revision_id=None):
 
2222
        """Make a complete copy of the content in self into destination.
 
2223
        
 
2224
        This is a destructive operation! Do not use it on existing 
 
2225
        repositories.
 
2226
 
 
2227
        :param revision_id: Only copy the content needed to construct
 
2228
                            revision_id and its parents.
 
2229
        """
 
2230
        try:
 
2231
            self.target.set_make_working_trees(self.source.make_working_trees())
 
2232
        except NotImplementedError:
 
2233
            pass
 
2234
        # TODO: jam 20070210 Internal, assert, don't translate
 
2235
        revision_id = osutils.safe_revision_id(revision_id)
 
2236
        # but don't bother fetching if we have the needed data now.
 
2237
        if (revision_id not in (None, _mod_revision.NULL_REVISION) and 
 
2238
            self.target.has_revision(revision_id)):
 
2239
            return
 
2240
        self.target.fetch(self.source, revision_id=revision_id)
 
2241
 
 
2242
 
 
2243
class InterKnit1and2(InterKnitRepo):
 
2244
 
 
2245
    @classmethod
 
2246
    def _get_repo_format_to_test(self):
 
2247
        return None
 
2248
 
 
2249
    @staticmethod
 
2250
    def is_compatible(source, target):
 
2251
        """Be compatible with Knit1 source and Knit3 target"""
 
2252
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit3
 
2253
        try:
 
2254
            from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1, \
 
2255
                    RepositoryFormatKnit3
 
2256
            return (isinstance(source._format, (RepositoryFormatKnit1)) and
 
2257
                    isinstance(target._format, (RepositoryFormatKnit3)))
 
2258
        except AttributeError:
 
2259
            return False
 
2260
 
 
2261
    @needs_write_lock
 
2262
    def fetch(self, revision_id=None, pb=None):
 
2263
        """See InterRepository.fetch()."""
 
2264
        from bzrlib.fetch import Knit1to2Fetcher
 
2265
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
 
2266
               self.source, self.source._format, self.target, 
 
2267
               self.target._format)
 
2268
        # TODO: jam 20070210 This should be an assert, not a translate
 
2269
        revision_id = osutils.safe_revision_id(revision_id)
 
2270
        f = Knit1to2Fetcher(to_repository=self.target,
 
2271
                            from_repository=self.source,
 
2272
                            last_revision=revision_id,
 
2273
                            pb=pb)
 
2274
        return f.count_copied, f.failed_revisions
 
2275
 
 
2276
 
 
2277
class InterRemoteRepository(InterRepository):
 
2278
    """Code for converting between RemoteRepository objects.
 
2279
 
 
2280
    This just gets an non-remote repository from the RemoteRepository, and calls
 
2281
    InterRepository.get again.
 
2282
    """
 
2283
 
 
2284
    def __init__(self, source, target):
 
2285
        if isinstance(source, remote.RemoteRepository):
 
2286
            source._ensure_real()
 
2287
            real_source = source._real_repository
 
2288
        else:
 
2289
            real_source = source
 
2290
        if isinstance(target, remote.RemoteRepository):
 
2291
            target._ensure_real()
 
2292
            real_target = target._real_repository
 
2293
        else:
 
2294
            real_target = target
 
2295
        self.real_inter = InterRepository.get(real_source, real_target)
 
2296
 
 
2297
    @staticmethod
 
2298
    def is_compatible(source, target):
 
2299
        if isinstance(source, remote.RemoteRepository):
 
2300
            return True
 
2301
        if isinstance(target, remote.RemoteRepository):
 
2302
            return True
 
2303
        return False
 
2304
 
 
2305
    def copy_content(self, revision_id=None):
 
2306
        self.real_inter.copy_content(revision_id=revision_id)
 
2307
 
 
2308
    def fetch(self, revision_id=None, pb=None):
 
2309
        self.real_inter.fetch(revision_id=revision_id, pb=pb)
 
2310
 
 
2311
    @classmethod
 
2312
    def _get_repo_format_to_test(self):
 
2313
        return None
 
2314
 
 
2315
 
 
2316
InterRepository.register_optimiser(InterSameDataRepository)
 
2317
InterRepository.register_optimiser(InterWeaveRepo)
 
2318
InterRepository.register_optimiser(InterKnitRepo)
 
2319
InterRepository.register_optimiser(InterModel1and2)
 
2320
InterRepository.register_optimiser(InterKnit1and2)
 
2321
InterRepository.register_optimiser(InterRemoteRepository)
 
2322
 
 
2323
 
 
2324
class CopyConverter(object):
 
2325
    """A repository conversion tool which just performs a copy of the content.
 
2326
    
 
2327
    This is slow but quite reliable.
 
2328
    """
 
2329
 
 
2330
    def __init__(self, target_format):
 
2331
        """Create a CopyConverter.
 
2332
 
 
2333
        :param target_format: The format the resulting repository should be.
 
2334
        """
 
2335
        self.target_format = target_format
 
2336
        
 
2337
    def convert(self, repo, pb):
 
2338
        """Perform the conversion of to_convert, giving feedback via pb.
 
2339
 
 
2340
        :param to_convert: The disk object to convert.
 
2341
        :param pb: a progress bar to use for progress information.
 
2342
        """
 
2343
        self.pb = pb
 
2344
        self.count = 0
 
2345
        self.total = 4
 
2346
        # this is only useful with metadir layouts - separated repo content.
 
2347
        # trigger an assertion if not such
 
2348
        repo._format.get_format_string()
 
2349
        self.repo_dir = repo.bzrdir
 
2350
        self.step('Moving repository to repository.backup')
 
2351
        self.repo_dir.transport.move('repository', 'repository.backup')
 
2352
        backup_transport =  self.repo_dir.transport.clone('repository.backup')
 
2353
        repo._format.check_conversion_target(self.target_format)
 
2354
        self.source_repo = repo._format.open(self.repo_dir,
 
2355
            _found=True,
 
2356
            _override_transport=backup_transport)
 
2357
        self.step('Creating new repository')
 
2358
        converted = self.target_format.initialize(self.repo_dir,
 
2359
                                                  self.source_repo.is_shared())
 
2360
        converted.lock_write()
 
2361
        try:
 
2362
            self.step('Copying content into repository.')
 
2363
            self.source_repo.copy_content_into(converted)
 
2364
        finally:
 
2365
            converted.unlock()
 
2366
        self.step('Deleting old repository content.')
 
2367
        self.repo_dir.transport.delete_tree('repository.backup')
 
2368
        self.pb.note('repository converted')
 
2369
 
 
2370
    def step(self, message):
 
2371
        """Update the pb by a step."""
 
2372
        self.count +=1
 
2373
        self.pb.update(message, self.count, self.total)
 
2374
 
 
2375
 
 
2376
_unescape_map = {
 
2377
    'apos':"'",
 
2378
    'quot':'"',
 
2379
    'amp':'&',
 
2380
    'lt':'<',
 
2381
    'gt':'>'
 
2382
}
 
2383
 
 
2384
 
 
2385
def _unescaper(match, _map=_unescape_map):
 
2386
    code = match.group(1)
 
2387
    try:
 
2388
        return _map[code]
 
2389
    except KeyError:
 
2390
        if not code.startswith('#'):
 
2391
            raise
 
2392
        return unichr(int(code[1:])).encode('utf8')
 
2393
 
 
2394
 
 
2395
_unescape_re = None
 
2396
 
 
2397
 
 
2398
def _unescape_xml(data):
 
2399
    """Unescape predefined XML entities in a string of data."""
 
2400
    global _unescape_re
 
2401
    if _unescape_re is None:
 
2402
        _unescape_re = re.compile('\&([^;]*);')
 
2403
    return _unescape_re.sub(_unescaper, data)