/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Aaron Bentley
  • Date: 2006-08-17 19:51:54 UTC
  • mto: (1910.2.43 format-bumps)
  • mto: This revision was merged to the branch mainline in revision 1997.
  • Revision ID: abentley@panoramicfeedback.com-20060817195154-af960bfc59351ebf
Implement knit repo format 2

Show diffs side-by-side

added added

removed removed

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