~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Martin Pool
  • Date: 2006-03-23 19:00:53 UTC
  • mto: This revision was merged to the branch mainline in revision 1626.
  • Revision ID: mbp@sourcefrog.net-20060323190053-ac6b735ff74f56d7
Handle 'bzr ?', etc.

Show diffs side-by-side

added added

removed removed

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