/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
1
# Copyright (C) 2005 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
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
16
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
17
from copy import deepcopy
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
18
from cStringIO import StringIO
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
19
from unittest import TestSuite
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
20
import xml.sax.saxutils
21
1185.65.10 by Robert Collins
Rename Controlfiles to LockableFiles.
22
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
23
import bzrlib.bzrdir as bzrdir
1534.4.28 by Robert Collins
first cut at merge from integration.
24
from bzrlib.decorators import needs_read_lock, needs_write_lock
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
25
import bzrlib.errors as errors
1534.4.28 by Robert Collins
first cut at merge from integration.
26
from bzrlib.errors import InvalidRevisionId
1185.66.3 by Aaron Bentley
Renamed ControlFiles to LockableFiles
27
from bzrlib.lockable_files import LockableFiles
1534.4.28 by Robert Collins
first cut at merge from integration.
28
from bzrlib.osutils import safe_unicode
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
29
from bzrlib.revision import NULL_REVISION
1185.65.14 by Robert Collins
Merge from aaron. Whee, we are synced. Yay. Begone the foul demons of merge conflicts.
30
from bzrlib.store import copy_all
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
31
from bzrlib.store.weave import WeaveStore
32
from bzrlib.store.text import TextStore
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
33
from bzrlib.symbol_versioning import *
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
34
from bzrlib.trace import mutter
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
35
from bzrlib.tree import RevisionTree
36
from bzrlib.testament import Testament
1534.4.28 by Robert Collins
first cut at merge from integration.
37
from bzrlib.tree import EmptyTree
1185.79.2 by John Arbash Meinel
Adding progress bars to copy_all and copy_multi, fixing ordering of repository.clone() to pull inventories after weaves.
38
import bzrlib.ui
1534.4.28 by Robert Collins
first cut at merge from integration.
39
import bzrlib.xml5
1185.70.3 by Martin Pool
Various updates to make storage branch mergeable:
40
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
41
1185.66.5 by Aaron Bentley
Renamed RevisionStorage to Repository
42
class Repository(object):
1185.70.3 by Martin Pool
Various updates to make storage branch mergeable:
43
    """Repository holding history for one or more branches.
44
45
    The repository holds and retrieves historical information including
46
    revisions and file history.  It's normally accessed only by the Branch,
47
    which views a particular line of development through that history.
48
49
    The Repository builds on top of Stores and a Transport, which respectively 
50
    describe the disk data format and the way of accessing the (possibly 
51
    remote) disk.
52
    """
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
53
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
54
    @needs_read_lock
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
55
    def _all_possible_ids(self):
56
        """Return all the possible revisions that we could find."""
57
        return self.get_inventory_weave().names()
58
59
    @needs_read_lock
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
60
    def all_revision_ids(self):
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
61
        """Returns a list of all the revision ids in the repository. 
62
63
        These are in as much topological order as the underlying store can 
64
        present: for weaves ghosts may lead to a lack of correctness until
65
        the reweave updates the parents list.
66
        """
67
        result = self._all_possible_ids()
68
        return self._eliminate_revisions_not_present(result)
69
70
    @needs_read_lock
71
    def _eliminate_revisions_not_present(self, revision_ids):
72
        """Check every revision id in revision_ids to see if we have it.
73
74
        Returns a set of the present revisions.
75
        """
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
76
        result = []
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
77
        for id in revision_ids:
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
78
            if self.has_revision(id):
79
               result.append(id)
80
        return result
81
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
82
    @staticmethod
83
    def create(a_bzrdir):
84
        """Construct the current default format repository in a_bzrdir."""
85
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
86
87
    def __init__(self, transport, branch_format, _format=None, a_bzrdir=None):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
88
        object.__init__(self)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
89
        if transport is not None:
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
90
            warn("Repository.__init__(..., transport=XXX): The transport parameter is "
91
                 "deprecated and was never in a supported release. Please use "
92
                 "bzrdir.open_repository() or bzrdir.open_branch().repository.",
93
                 DeprecationWarning,
94
                 stacklevel=2)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
95
            self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
96
        else: 
97
            # TODO: clone into repository if needed
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
98
            self.control_files = LockableFiles(a_bzrdir.get_repository_transport(None), 'README')
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
99
100
        dir_mode = self.control_files._dir_mode
101
        file_mode = self.control_files._file_mode
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
102
        self._format = _format
103
        self.bzrdir = a_bzrdir
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
104
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
105
        def get_weave(name, prefixed=False):
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
106
            if name:
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
107
                name = safe_unicode(name)
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
108
            else:
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
109
                name = ''
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
110
            relpath = self.control_files._escape(name)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
111
            weave_transport = self.control_files._transport.clone(relpath)
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
112
            ws = WeaveStore(weave_transport, prefixed=prefixed,
113
                            dir_mode=dir_mode,
114
                            file_mode=file_mode)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
115
            if self.control_files._transport.should_cache():
116
                ws.enable_cache = True
117
            return ws
118
1534.4.28 by Robert Collins
first cut at merge from integration.
119
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
120
        def get_store(name, compressed=True, prefixed=False):
121
            # FIXME: This approach of assuming stores are all entirely compressed
122
            # or entirely uncompressed is tidy, but breaks upgrade from 
123
            # some existing branches where there's a mixture; we probably 
124
            # still want the option to look for both.
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
125
            if name:
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
126
                name = safe_unicode(name)
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
127
            else:
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
128
                name = ''
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
129
            relpath = self.control_files._escape(name)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
130
            store = TextStore(self.control_files._transport.clone(relpath),
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
131
                              prefixed=prefixed, compressed=compressed,
132
                              dir_mode=dir_mode,
133
                              file_mode=file_mode)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
134
            #if self._transport.should_cache():
135
            #    cache_path = os.path.join(self.cache_root, name)
136
            #    os.mkdir(cache_path)
137
            #    store = bzrlib.store.CachedStore(store, cache_path)
138
            return store
139
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
140
        if branch_format is not None:
141
            # circular dependencies:
142
            from bzrlib.branch import (BzrBranchFormat4,
143
                                       BzrBranchFormat5,
144
                                       BzrBranchFormat6,
145
                                       )
146
            if isinstance(branch_format, BzrBranchFormat4):
147
                self._format = RepositoryFormat4()
148
            elif isinstance(branch_format, BzrBranchFormat5):
149
                self._format = RepositoryFormat5()
150
            elif isinstance(branch_format, BzrBranchFormat6):
151
                self._format = RepositoryFormat6()
152
            
1534.4.28 by Robert Collins
first cut at merge from integration.
153
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
154
        if isinstance(self._format, RepositoryFormat4):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
155
            self.inventory_store = get_store('inventory-store')
156
            self.text_store = get_store('text-store')
157
            self.revision_store = get_store('revision-store')
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
158
        elif isinstance(self._format, RepositoryFormat5):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
159
            self.control_weaves = get_weave('')
160
            self.weave_store = get_weave('weaves')
161
            self.revision_store = get_store('revision-store', compressed=False)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
162
        elif isinstance(self._format, RepositoryFormat6):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
163
            self.control_weaves = get_weave('')
164
            self.weave_store = get_weave('weaves', prefixed=True)
165
            self.revision_store = get_store('revision-store', compressed=False,
166
                                            prefixed=True)
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
167
        elif isinstance(self._format, RepositoryFormat7):
168
            self.control_weaves = get_weave('')
169
            self.weave_store = get_weave('weaves', prefixed=True)
170
            self.revision_store = get_store('revision-store', compressed=False,
171
                                            prefixed=True)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
172
        self.revision_store.register_suffix('sig')
173
174
    def lock_write(self):
175
        self.control_files.lock_write()
176
177
    def lock_read(self):
178
        self.control_files.lock_read()
179
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
180
    @needs_read_lock
181
    def missing_revision_ids(self, other, revision_id=None):
182
        """Return the revision ids that other has that this does not.
183
        
184
        These are returned in topological order.
185
186
        revision_id: only return revision ids included by revision_id.
187
        """
188
        if self._compatible_formats(other):
189
            # fast path for weave-inventory based stores.
190
            # we want all revisions to satisft revision_id in other.
191
            # but we dont want to stat every file here and there.
192
            # we want then, all revisions other needs to satisfy revision_id 
193
            # checked, but not those that we have locally.
194
            # so the first thing is to get a subset of the revisions to 
195
            # satisfy revision_id in other, and then eliminate those that
196
            # we do already have. 
197
            # this is slow on high latency connection to self, but as as this
198
            # disk format scales terribly for push anyway due to rewriting 
199
            # inventory.weave, this is considered acceptable.
200
            # - RBC 20060209
201
            if revision_id is not None:
202
                other_ids = other.get_ancestry(revision_id)
203
                assert other_ids.pop(0) == None
204
            else:
205
                other_ids = other._all_possible_ids()
206
            other_ids_set = set(other_ids)
207
            # other ids is the worst case to pull now.
208
            # now we want to filter other_ids against what we actually
209
            # have, but dont try to stat what we know we dont.
210
            my_ids = set(self._all_possible_ids())
211
            possibly_present_revisions = my_ids.intersection(other_ids_set)
212
            actually_present_revisions = set(self._eliminate_revisions_not_present(possibly_present_revisions))
213
            required_revisions = other_ids_set.difference(actually_present_revisions)
214
            required_topo_revisions = [rev_id for rev_id in other_ids if rev_id in required_revisions]
215
            if revision_id is not None:
216
                # we used get_ancestry to determine other_ids then we are assured all
217
                # revisions referenced are present as they are installed in topological order.
218
                return required_topo_revisions
219
            else:
220
                # we only have an estimate of whats available
221
                return other._eliminate_revisions_not_present(required_topo_revisions)
222
        # slow code path.
223
        my_ids = set(self.all_revision_ids())
224
        if revision_id is not None:
225
            other_ids = other.get_ancestry(revision_id)
226
            assert other_ids.pop(0) == None
227
        else:
228
            other_ids = other.all_revision_ids()
229
        result_set = set(other_ids).difference(my_ids)
230
        return [rev_id for rev_id in other_ids if rev_id in result_set]
231
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
232
    @staticmethod
233
    def open(base):
234
        """Open the repository rooted at base.
235
236
        For instance, if the repository is at URL/.bzr/repository,
237
        Repository.open(URL) -> a Repository instance.
238
        """
239
        control = bzrdir.BzrDir.open(base)
240
        return control.open_repository()
241
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
242
    def _compatible_formats(self, other):
243
        """Return True if the stores in self and other are 'compatible'
244
        
245
        'compatible' means that they are both the same underlying type
246
        i.e. both weave stores, or both knits and thus support fast-path
247
        operations."""
248
        return (isinstance(self._format, (RepositoryFormat5,
249
                                          RepositoryFormat6,
250
                                          RepositoryFormat7)) and
251
                isinstance(other._format, (RepositoryFormat5,
252
                                           RepositoryFormat6,
253
                                           RepositoryFormat7)))
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
254
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
255
    @needs_read_lock
256
    def copy_content_into(self, destination, revision_id=None, basis=None):
257
        """Make a complete copy of the content in self into destination."""
258
        destination.lock_write()
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
259
        try:
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
260
            # optimised paths:
261
            # compatible stores
262
            if self._compatible_formats(destination):
263
                if basis is not None:
264
                    # copy the basis in, then fetch remaining data.
265
                    basis.copy_content_into(destination, revision_id)
266
                    destination.fetch(self, revision_id=revision_id)
267
                else:
268
                    # FIXME do not peek!
269
                    if self.control_files._transport.listable():
1185.79.2 by John Arbash Meinel
Adding progress bars to copy_all and copy_multi, fixing ordering of repository.clone() to pull inventories after weaves.
270
                        pb = bzrlib.ui.ui_factory.progress_bar()
1185.79.3 by John Arbash Meinel
cleanups from Robert
271
                        copy_all(self.weave_store,
272
                            destination.weave_store, pb=pb)
1185.79.2 by John Arbash Meinel
Adding progress bars to copy_all and copy_multi, fixing ordering of repository.clone() to pull inventories after weaves.
273
                        pb.update('copying inventory', 0, 1)
1185.79.3 by John Arbash Meinel
cleanups from Robert
274
                        destination.control_weaves.copy_multi(
275
                            self.control_weaves, ['inventory'])
276
                        copy_all(self.revision_store,
277
                            destination.revision_store, pb=pb)
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
278
                    else:
279
                        destination.fetch(self, revision_id=revision_id)
280
            # compatible v4 stores
281
            elif isinstance(self._format, RepositoryFormat4):
282
                if not isinstance(destination._format, RepositoryFormat4):
283
                    raise BzrError('cannot copy v4 branches to anything other than v4 branches.')
284
                store_pairs = ((self.text_store,      destination.text_store),
285
                               (self.inventory_store, destination.inventory_store),
286
                               (self.revision_store,  destination.revision_store))
287
                try:
288
                    for from_store, to_store in store_pairs: 
289
                        copy_all(from_store, to_store)
290
                except UnlistableStore:
291
                    raise UnlistableBranch(from_store)
292
            # fallback - 'fetch'
293
            else:
294
                destination.fetch(self, revision_id=revision_id)
295
        finally:
296
            destination.unlock()
297
298
    @needs_write_lock
299
    def fetch(self, source, revision_id=None):
300
        """Fetch the content required to construct revision_id from source.
301
302
        If revision_id is None all content is copied.
303
        """
304
        from bzrlib.fetch import RepoFetcher
305
        mutter("Using fetch logic to copy between %s(%s) and %s(%s)",
306
               source, source._format, self, self._format)
307
        RepoFetcher(to_repository=self, from_repository=source, last_revision=revision_id)
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
308
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
309
    def unlock(self):
310
        self.control_files.unlock()
311
1185.65.27 by Robert Collins
Tweak storage towards mergability.
312
    @needs_read_lock
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
313
    def clone(self, a_bzrdir, revision_id=None, basis=None):
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
314
        """Clone this repository into a_bzrdir using the current format.
315
316
        Currently no check is made that the format of this repository and
317
        the bzrdir format are compatible. FIXME RBC 20060201.
318
        """
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
319
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
320
            # use target default format.
321
            result = a_bzrdir.create_repository()
322
        # FIXME RBC 20060209 split out the repository type to avoid this check ?
323
        elif isinstance(a_bzrdir._format,
324
                      (bzrdir.BzrDirFormat4,
325
                       bzrdir.BzrDirFormat5,
326
                       bzrdir.BzrDirFormat6)):
327
            result = a_bzrdir.open_repository()
328
        else:
329
            result = self._format.initialize(a_bzrdir)
330
        self.copy_content_into(result, revision_id, basis)
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
331
        return result
332
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
333
    def has_revision(self, revision_id):
334
        """True if this branch has a copy of the revision.
335
336
        This does not necessarily imply the revision is merge
337
        or on the mainline."""
338
        return (revision_id is None
339
                or self.revision_store.has_id(revision_id))
340
341
    @needs_read_lock
342
    def get_revision_xml_file(self, revision_id):
343
        """Return XML file object for revision object."""
344
        if not revision_id or not isinstance(revision_id, basestring):
345
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
346
        try:
347
            return self.revision_store.get(revision_id)
348
        except (IndexError, KeyError):
349
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
350
1185.65.27 by Robert Collins
Tweak storage towards mergability.
351
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
352
    def get_revision_xml(self, revision_id):
353
        return self.get_revision_xml_file(revision_id).read()
354
1185.65.27 by Robert Collins
Tweak storage towards mergability.
355
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
356
    def get_revision(self, revision_id):
357
        """Return the Revision object for a named revision"""
358
        xml_file = self.get_revision_xml_file(revision_id)
359
360
        try:
361
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
362
        except SyntaxError, e:
363
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
364
                                         [revision_id,
365
                                          str(e)])
366
            
367
        assert r.revision_id == revision_id
368
        return r
369
1185.65.27 by Robert Collins
Tweak storage towards mergability.
370
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
371
    def get_revision_sha1(self, revision_id):
372
        """Hash the stored value of a revision, and return it."""
373
        # In the future, revision entries will be signed. At that
374
        # point, it is probably best *not* to include the signature
375
        # in the revision hash. Because that lets you re-sign
376
        # the revision, (add signatures/remove signatures) and still
377
        # have all hash pointers stay consistent.
378
        # But for now, just hash the contents.
379
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
380
381
    @needs_write_lock
382
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
383
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
384
                                revision_id, "sig")
385
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
386
    def fileid_involved_between_revs(self, from_revid, to_revid):
387
        """Find file_id(s) which are involved in the changes between revisions.
388
389
        This determines the set of revisions which are involved, and then
390
        finds all file ids affected by those revisions.
391
        """
392
        # TODO: jam 20060119 This code assumes that w.inclusions will
393
        #       always be correct. But because of the presence of ghosts
394
        #       it is possible to be wrong.
395
        #       One specific example from Robert Collins:
396
        #       Two branches, with revisions ABC, and AD
397
        #       C is a ghost merge of D.
398
        #       Inclusions doesn't recognize D as an ancestor.
399
        #       If D is ever merged in the future, the weave
400
        #       won't be fixed, because AD never saw revision C
401
        #       to cause a conflict which would force a reweave.
402
        w = self.get_inventory_weave()
403
        from_set = set(w.inclusions([w.lookup(from_revid)]))
404
        to_set = set(w.inclusions([w.lookup(to_revid)]))
405
        included = to_set.difference(from_set)
406
        changed = map(w.idx_to_name, included)
407
        return self._fileid_involved_by_set(changed)
408
409
    def fileid_involved(self, last_revid=None):
410
        """Find all file_ids modified in the ancestry of last_revid.
411
412
        :param last_revid: If None, last_revision() will be used.
413
        """
414
        w = self.get_inventory_weave()
415
        if not last_revid:
416
            changed = set(w._names)
417
        else:
418
            included = w.inclusions([w.lookup(last_revid)])
419
            changed = map(w.idx_to_name, included)
420
        return self._fileid_involved_by_set(changed)
421
422
    def fileid_involved_by_set(self, changes):
423
        """Find all file_ids modified by the set of revisions passed in.
424
425
        :param changes: A set() of revision ids
426
        """
427
        # TODO: jam 20060119 This line does *nothing*, remove it.
428
        #       or better yet, change _fileid_involved_by_set so
429
        #       that it takes the inventory weave, rather than
430
        #       pulling it out by itself.
431
        return self._fileid_involved_by_set(changes)
432
433
    def _fileid_involved_by_set(self, changes):
434
        """Find the set of file-ids affected by the set of revisions.
435
436
        :param changes: A set() of revision ids.
437
        :return: A set() of file ids.
438
        
439
        This peaks at the Weave, interpreting each line, looking to
440
        see if it mentions one of the revisions. And if so, includes
441
        the file id mentioned.
442
        This expects both the Weave format, and the serialization
443
        to have a single line per file/directory, and to have
444
        fileid="" and revision="" on that line.
445
        """
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
446
        assert isinstance(self._format, (RepositoryFormat5,
447
                                         RepositoryFormat6,
448
                                         RepositoryFormat7)), \
449
            "fileid_involved only supported for branches which store inventory as unnested xml"
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
450
451
        w = self.get_inventory_weave()
452
        file_ids = set()
453
        for line in w._weave:
454
455
            # it is ugly, but it is due to the weave structure
456
            if not isinstance(line, basestring): continue
457
458
            start = line.find('file_id="')+9
459
            if start < 9: continue
460
            end = line.find('"', start)
461
            assert end>= 0
462
            file_id = xml.sax.saxutils.unescape(line[start:end])
463
464
            # check if file_id is already present
465
            if file_id in file_ids: continue
466
467
            start = line.find('revision="')+10
468
            if start < 10: continue
469
            end = line.find('"', start)
470
            assert end>= 0
471
            revision_id = xml.sax.saxutils.unescape(line[start:end])
472
473
            if revision_id in changes:
474
                file_ids.add(file_id)
475
        return file_ids
476
1185.65.27 by Robert Collins
Tweak storage towards mergability.
477
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
478
    def get_inventory_weave(self):
479
        return self.control_weaves.get_weave('inventory',
480
            self.get_transaction())
481
1185.65.27 by Robert Collins
Tweak storage towards mergability.
482
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
483
    def get_inventory(self, revision_id):
484
        """Get Inventory object by hash."""
485
        xml = self.get_inventory_xml(revision_id)
486
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
487
1185.65.27 by Robert Collins
Tweak storage towards mergability.
488
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
489
    def get_inventory_xml(self, revision_id):
490
        """Get inventory XML as a file object."""
491
        try:
492
            assert isinstance(revision_id, basestring), type(revision_id)
493
            iw = self.get_inventory_weave()
494
            return iw.get_text(iw.lookup(revision_id))
495
        except IndexError:
496
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
497
1185.65.27 by Robert Collins
Tweak storage towards mergability.
498
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
499
    def get_inventory_sha1(self, revision_id):
500
        """Return the sha1 hash of the inventory entry
501
        """
502
        return self.get_revision(revision_id).inventory_sha1
503
1185.65.27 by Robert Collins
Tweak storage towards mergability.
504
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
505
    def get_revision_inventory(self, revision_id):
506
        """Return inventory of a past revision."""
507
        # TODO: Unify this with get_inventory()
508
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
509
        # must be the same as its revision, so this is trivial.
1534.4.28 by Robert Collins
first cut at merge from integration.
510
        if revision_id is None:
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
511
            # This does not make sense: if there is no revision,
512
            # then it is the current tree inventory surely ?!
513
            # and thus get_root_id() is something that looks at the last
514
            # commit on the branch, and the get_root_id is an inventory check.
515
            raise NotImplementedError
516
            # return Inventory(self.get_root_id())
517
        else:
518
            return self.get_inventory(revision_id)
519
1185.65.27 by Robert Collins
Tweak storage towards mergability.
520
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
521
    def revision_tree(self, revision_id):
522
        """Return Tree for a revision on this branch.
523
524
        `revision_id` may be None for the null revision, in which case
525
        an `EmptyTree` is returned."""
526
        # TODO: refactor this to use an existing revision object
527
        # so we don't need to read it in twice.
1534.4.28 by Robert Collins
first cut at merge from integration.
528
        if revision_id is None or revision_id == NULL_REVISION:
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
529
            return EmptyTree()
530
        else:
531
            inv = self.get_revision_inventory(revision_id)
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
532
            return RevisionTree(self, inv, revision_id)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
533
1185.65.27 by Robert Collins
Tweak storage towards mergability.
534
    @needs_read_lock
1185.66.2 by Aaron Bentley
Moved get_ancestry to RevisionStorage
535
    def get_ancestry(self, revision_id):
536
        """Return a list of revision-ids integrated by a revision.
537
        
538
        This is topologically sorted.
539
        """
540
        if revision_id is None:
541
            return [None]
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
542
        if not self.has_revision(revision_id):
543
            raise errors.NoSuchRevision(self, revision_id)
1185.66.2 by Aaron Bentley
Moved get_ancestry to RevisionStorage
544
        w = self.get_inventory_weave()
545
        return [None] + map(w.idx_to_name,
546
                            w.inclusions([w.lookup(revision_id)]))
547
1185.65.4 by Aaron Bentley
Fixed cat command
548
    @needs_read_lock
549
    def print_file(self, file, revision_id):
1185.65.29 by Robert Collins
Implement final review suggestions.
550
        """Print `file` to stdout.
551
        
552
        FIXME RBC 20060125 as John Meinel points out this is a bad api
553
        - it writes to stdout, it assumes that that is valid etc. Fix
554
        by creating a new more flexible convenience function.
555
        """
1185.65.4 by Aaron Bentley
Fixed cat command
556
        tree = self.revision_tree(revision_id)
557
        # use inventory as it was in that revision
558
        file_id = tree.inventory.path2id(file)
559
        if not file_id:
560
            raise BzrError("%r is not present in revision %s" % (file, revno))
1185.65.15 by Robert Collins
Merge from integration.
561
            try:
562
                revno = self.revision_id_to_revno(revision_id)
563
            except errors.NoSuchRevision:
564
                # TODO: This should not be BzrError,
565
                # but NoSuchFile doesn't fit either
566
                raise BzrError('%r is not present in revision %s' 
567
                                % (file, revision_id))
568
            else:
569
                raise BzrError('%r is not present in revision %s'
570
                                % (file, revno))
1185.65.4 by Aaron Bentley
Fixed cat command
571
        tree.print_file(file_id)
572
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
573
    def get_transaction(self):
574
        return self.control_files.get_transaction()
575
1185.65.27 by Robert Collins
Tweak storage towards mergability.
576
    @needs_write_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
577
    def sign_revision(self, revision_id, gpg_strategy):
578
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
579
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
580
581
582
class RepositoryFormat(object):
583
    """A repository format.
584
585
    Formats provide three things:
586
     * An initialization routine to construct repository data on disk.
587
     * a format string which is used when the BzrDir supports versioned
588
       children.
589
     * an open routine which returns a Repository instance.
590
591
    Formats are placed in an dict by their format string for reference 
592
    during opening. These should be subclasses of RepositoryFormat
593
    for consistency.
594
595
    Once a format is deprecated, just deprecate the initialize and open
596
    methods on the format class. Do not deprecate the object, as the 
597
    object will be created every system load.
598
599
    Common instance attributes:
600
    _matchingbzrdir - the bzrdir format that the repository format was
601
    originally written to work with. This can be used if manually
602
    constructing a bzrdir and repository, or more commonly for test suite
603
    parameterisation.
604
    """
605
606
    _default_format = None
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
607
    """The default format used for new repositories."""
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
608
609
    _formats = {}
610
    """The known formats."""
611
612
    @classmethod
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
613
    def find_format(klass, a_bzrdir):
614
        """Return the format for the repository object in a_bzrdir."""
615
        try:
616
            transport = a_bzrdir.get_repository_transport(None)
617
            format_string = transport.get("format").read()
618
            return klass._formats[format_string]
619
        except errors.NoSuchFile:
620
            raise errors.NoRepositoryPresent(a_bzrdir)
621
        except KeyError:
622
            raise errors.UnknownFormatError(format_string)
623
624
    @classmethod
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
625
    def get_default_format(klass):
626
        """Return the current default format."""
627
        return klass._default_format
628
629
    def get_format_string(self):
630
        """Return the ASCII format string that identifies this format.
631
        
632
        Note that in pre format ?? repositories the format string is 
633
        not permitted nor written to disk.
634
        """
635
        raise NotImplementedError(self.get_format_string)
636
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
637
    def initialize(self, a_bzrdir, _internal=False):
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
638
        """Create a weave repository.
639
        
640
        TODO: when creating split out bzr branch formats, move this to a common
641
        base for Format5, Format6. or something like that.
642
        """
643
        from bzrlib.weavefile import write_weave_v5
644
        from bzrlib.weave import Weave
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
645
646
        if not _internal:
647
            # always initialized when the bzrdir is.
648
            return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
649
        
650
        # Create an empty weave
651
        sio = StringIO()
652
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
653
        empty_weave = sio.getvalue()
654
655
        mutter('creating repository in %s.', a_bzrdir.transport.base)
656
        dirs = ['revision-store', 'weaves']
657
        lock_file = 'branch-lock'
658
        files = [('inventory.weave', StringIO(empty_weave)), 
659
                 ]
660
        
661
        # FIXME: RBC 20060125 dont peek under the covers
662
        # NB: no need to escape relative paths that are url safe.
663
        control_files = LockableFiles(a_bzrdir.transport, 'branch-lock')
664
        control_files.lock_write()
665
        control_files._transport.mkdir_multi(dirs,
666
                mode=control_files._dir_mode)
667
        try:
668
            for file, content in files:
669
                control_files.put(file, content)
670
        finally:
671
            control_files.unlock()
672
        return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
673
674
    def is_supported(self):
675
        """Is this format supported?
676
677
        Supported formats must be initializable and openable.
678
        Unsupported formats may not support initialization or committing or 
679
        some other features depending on the reason for not being supported.
680
        """
681
        return True
682
683
    def open(self, a_bzrdir, _found=False):
684
        """Return an instance of this format for the bzrdir a_bzrdir.
685
        
686
        _found is a private parameter, do not use it.
687
        """
688
        if not _found:
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
689
            # we are being called directly and must probe.
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
690
            raise NotImplementedError
691
        return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
692
693
    @classmethod
694
    def register_format(klass, format):
695
        klass._formats[format.get_format_string()] = format
696
697
    @classmethod
698
    def set_default_format(klass, format):
699
        klass._default_format = format
700
701
    @classmethod
702
    def unregister_format(klass, format):
703
        assert klass._formats[format.get_format_string()] is format
704
        del klass._formats[format.get_format_string()]
705
706
707
class RepositoryFormat4(RepositoryFormat):
708
    """Bzr repository format 4.
709
710
    This repository format has:
711
     - flat stores
712
     - TextStores for texts, inventories,revisions.
713
714
    This format is deprecated: it indexes texts using a text id which is
715
    removed in format 5; initializationa and write support for this format
716
    has been removed.
717
    """
718
719
    def __init__(self):
720
        super(RepositoryFormat4, self).__init__()
721
        self._matchingbzrdir = bzrdir.BzrDirFormat4()
722
1534.4.50 by Robert Collins
Got the bzrdir api straightened out, plenty of refactoring to use it pending, but the api is up and running.
723
    def initialize(self, url, _internal=False):
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
724
        """Format 4 branches cannot be created."""
725
        raise errors.UninitializableFormat(self)
726
727
    def is_supported(self):
728
        """Format 4 is not supported.
729
730
        It is not supported because the model changed from 4 to 5 and the
731
        conversion logic is expensive - so doing it on the fly was not 
732
        feasible.
733
        """
734
        return False
735
736
737
class RepositoryFormat5(RepositoryFormat):
738
    """Bzr control format 5.
739
740
    This repository format has:
741
     - weaves for file texts and inventory
742
     - flat stores
743
     - TextStores for revisions and signatures.
744
    """
745
746
    def __init__(self):
747
        super(RepositoryFormat5, self).__init__()
748
        self._matchingbzrdir = bzrdir.BzrDirFormat5()
749
750
751
class RepositoryFormat6(RepositoryFormat):
752
    """Bzr control format 6.
753
754
    This repository format has:
755
     - weaves for file texts and inventory
756
     - hash subdirectory based stores.
757
     - TextStores for revisions and signatures.
758
    """
759
760
    def __init__(self):
761
        super(RepositoryFormat6, self).__init__()
762
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
763
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
764
765
class RepositoryFormat7(RepositoryFormat):
766
    """Bzr repository 7.
767
768
    This repository format has:
769
     - weaves for file texts and inventory
770
     - hash subdirectory based stores.
771
     - TextStores for revisions and signatures.
772
     - a format marker of its own
773
    """
774
775
    def get_format_string(self):
776
        """See RepositoryFormat.get_format_string()."""
777
        return "Bazaar-NG Repository format 7"
778
779
    def initialize(self, a_bzrdir):
780
        """Create a weave repository.
781
        """
782
        from bzrlib.weavefile import write_weave_v5
783
        from bzrlib.weave import Weave
784
785
        # Create an empty weave
786
        sio = StringIO()
787
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
788
        empty_weave = sio.getvalue()
789
790
        mutter('creating repository in %s.', a_bzrdir.transport.base)
791
        dirs = ['revision-store', 'weaves']
792
        files = [('inventory.weave', StringIO(empty_weave)), 
793
                 ]
794
        utf8_files = [('format', self.get_format_string())]
795
        
796
        # FIXME: RBC 20060125 dont peek under the covers
797
        # NB: no need to escape relative paths that are url safe.
798
        lock_file = 'lock'
799
        repository_transport = a_bzrdir.get_repository_transport(self)
800
        repository_transport.put(lock_file, StringIO()) # TODO get the file mode from the bzrdir lock files., mode=file_mode)
801
        control_files = LockableFiles(repository_transport, 'lock')
802
        control_files.lock_write()
803
        control_files._transport.mkdir_multi(dirs,
804
                mode=control_files._dir_mode)
805
        try:
806
            for file, content in files:
807
                control_files.put(file, content)
808
            for file, content in utf8_files:
809
                control_files.put_utf8(file, content)
810
        finally:
811
            control_files.unlock()
812
        return Repository(None, branch_format=None, _format=self, a_bzrdir=a_bzrdir)
813
814
    def __init__(self):
815
        super(RepositoryFormat7, self).__init__()
816
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
817
818
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
819
# formats which have no format string are not discoverable
820
# and not independently creatable, so are not registered.
1534.4.47 by Robert Collins
Split out repository into .bzr/repository
821
__default_format = RepositoryFormat7()
822
RepositoryFormat.register_format(__default_format)
823
RepositoryFormat.set_default_format(__default_format)
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
824
_legacy_formats = [RepositoryFormat4(),
825
                   RepositoryFormat5(),
826
                   RepositoryFormat6()]
827
828
1534.4.41 by Robert Collins
Branch now uses BzrDir reasonably sanely.
829
# TODO: jam 20060108 Create a new branch format, and as part of upgrade
830
#       make sure that ancestry.weave is deleted (it is never used, but
831
#       used to be created)
832
1534.4.40 by Robert Collins
Add RepositoryFormats and allow bzrdir.open or create _repository to be used.
833
class RepositoryTestProviderAdapter(object):
834
    """A tool to generate a suite testing multiple repository formats at once.
835
836
    This is done by copying the test once for each transport and injecting
837
    the transport_server, transport_readonly_server, and bzrdir_format and
838
    repository_format classes into each copy. Each copy is also given a new id()
839
    to make it easy to identify.
840
    """
841
842
    def __init__(self, transport_server, transport_readonly_server, formats):
843
        self._transport_server = transport_server
844
        self._transport_readonly_server = transport_readonly_server
845
        self._formats = formats
846
    
847
    def adapt(self, test):
848
        result = TestSuite()
849
        for repository_format, bzrdir_format in self._formats:
850
            new_test = deepcopy(test)
851
            new_test.transport_server = self._transport_server
852
            new_test.transport_readonly_server = self._transport_readonly_server
853
            new_test.bzrdir_format = bzrdir_format
854
            new_test.repository_format = repository_format
855
            def make_new_test_id():
856
                new_id = "%s(%s)" % (new_test.id(), repository_format.__class__.__name__)
857
                return lambda: new_id
858
            new_test.id = make_new_test_id()
859
            result.addTest(new_test)
860
        return result