~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-09-20 02:40:52 UTC
  • mfrom: (2835.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20070920024052-y2l7r5o00zrpnr73
No longer propagate index differences automatically (Robert Collins)

Show diffs side-by-side

added added

removed removed

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