/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Aaron Bentley
  • Date: 2006-08-15 03:09:14 UTC
  • mto: (1910.2.43 format-bumps)
  • mto: This revision was merged to the branch mainline in revision 1922.
  • Revision ID: aaron.bentley@utoronto.ca-20060815030914-dfff3234aefe3819
Update for merge review, handle deprecations

Show diffs side-by-side

added added

removed removed

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