~bzr-pqm/bzr/bzr.dev

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
17
from cStringIO import StringIO
18
1534.4.28 by Robert Collins
first cut at merge from integration.
19
from bzrlib.decorators import needs_read_lock, needs_write_lock
20
from bzrlib.errors import InvalidRevisionId
1185.66.3 by Aaron Bentley
Renamed ControlFiles to LockableFiles
21
from bzrlib.lockable_files import LockableFiles
1534.4.28 by Robert Collins
first cut at merge from integration.
22
from bzrlib.osutils import safe_unicode
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
23
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.
24
from bzrlib.store import copy_all
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
25
from bzrlib.store.weave import WeaveStore
26
from bzrlib.store.text import TextStore
27
from bzrlib.tree import RevisionTree
28
from bzrlib.testament import Testament
1534.4.28 by Robert Collins
first cut at merge from integration.
29
from bzrlib.tree import EmptyTree
30
import bzrlib.xml5
1185.70.3 by Martin Pool
Various updates to make storage branch mergeable:
31
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
32
1185.65.14 by Robert Collins
Merge from aaron. Whee, we are synced. Yay. Begone the foul demons of merge conflicts.
33
1185.66.5 by Aaron Bentley
Renamed RevisionStorage to Repository
34
class Repository(object):
1185.70.3 by Martin Pool
Various updates to make storage branch mergeable:
35
    """Repository holding history for one or more branches.
36
37
    The repository holds and retrieves historical information including
38
    revisions and file history.  It's normally accessed only by the Branch,
39
    which views a particular line of development through that history.
40
41
    The Repository builds on top of Stores and a Transport, which respectively 
42
    describe the disk data format and the way of accessing the (possibly 
43
    remote) disk.
44
    """
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
45
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
46
    def __init__(self, transport, branch_format):
1534.4.28 by Robert Collins
first cut at merge from integration.
47
        # circular dependencies:
48
        from bzrlib.branch import (BzrBranchFormat4,
49
                                   BzrBranchFormat5,
50
                                   BzrBranchFormat6,
51
                                   )
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
52
        object.__init__(self)
1185.69.2 by John Arbash Meinel
Changed LockableFiles to take the root directory directly. Moved mode information into LockableFiles instead of Branch
53
        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
54
55
        dir_mode = self.control_files._dir_mode
56
        file_mode = self.control_files._file_mode
57
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
58
        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
59
            if name:
1534.4.28 by Robert Collins
first cut at merge from integration.
60
                name = bzrlib.BZRDIR + '/' + 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
61
            else:
62
                name = bzrlib.BZRDIR
63
            relpath = self.control_files._escape(name)
1185.67.12 by Aaron Bentley
Removed LockableFiles.make_transport
64
            weave_transport = transport.clone(relpath)
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
65
            ws = WeaveStore(weave_transport, prefixed=prefixed,
66
                            dir_mode=dir_mode,
67
                            file_mode=file_mode)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
68
            if self.control_files._transport.should_cache():
69
                ws.enable_cache = True
70
            return ws
71
1534.4.28 by Robert Collins
first cut at merge from integration.
72
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
73
        def get_store(name, compressed=True, prefixed=False):
74
            # FIXME: This approach of assuming stores are all entirely compressed
75
            # or entirely uncompressed is tidy, but breaks upgrade from 
76
            # some existing branches where there's a mixture; we probably 
77
            # 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
78
            if name:
1534.4.28 by Robert Collins
first cut at merge from integration.
79
                name = bzrlib.BZRDIR + '/' + 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
80
            else:
81
                name = bzrlib.BZRDIR
82
            relpath = self.control_files._escape(name)
1185.67.12 by Aaron Bentley
Removed LockableFiles.make_transport
83
            store = TextStore(transport.clone(relpath),
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
84
                              prefixed=prefixed, compressed=compressed,
85
                              dir_mode=dir_mode,
86
                              file_mode=file_mode)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
87
            #if self._transport.should_cache():
88
            #    cache_path = os.path.join(self.cache_root, name)
89
            #    os.mkdir(cache_path)
90
            #    store = bzrlib.store.CachedStore(store, cache_path)
91
            return store
92
1534.4.28 by Robert Collins
first cut at merge from integration.
93
94
        if isinstance(branch_format, BzrBranchFormat4):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
95
            self.inventory_store = get_store('inventory-store')
96
            self.text_store = get_store('text-store')
97
            self.revision_store = get_store('revision-store')
1534.4.28 by Robert Collins
first cut at merge from integration.
98
        elif isinstance(branch_format, BzrBranchFormat5):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
99
            self.control_weaves = get_weave('')
100
            self.weave_store = get_weave('weaves')
101
            self.revision_store = get_store('revision-store', compressed=False)
1534.4.28 by Robert Collins
first cut at merge from integration.
102
        elif isinstance(branch_format, BzrBranchFormat6):
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
103
            self.control_weaves = get_weave('')
104
            self.weave_store = get_weave('weaves', prefixed=True)
105
            self.revision_store = get_store('revision-store', compressed=False,
106
                                            prefixed=True)
107
        self.revision_store.register_suffix('sig')
108
109
    def lock_write(self):
110
        self.control_files.lock_write()
111
112
    def lock_read(self):
113
        self.control_files.lock_read()
114
115
    def unlock(self):
116
        self.control_files.unlock()
117
1185.65.27 by Robert Collins
Tweak storage towards mergability.
118
    @needs_read_lock
1185.66.8 by Aaron Bentley
Applied Jelmer's patch to make clone a branch operation
119
    def copy(self, destination):
1185.65.27 by Robert Collins
Tweak storage towards mergability.
120
        destination.lock_write()
121
        try:
122
            destination.control_weaves.copy_multi(self.control_weaves, 
1185.66.8 by Aaron Bentley
Applied Jelmer's patch to make clone a branch operation
123
                ['inventory'])
1185.65.27 by Robert Collins
Tweak storage towards mergability.
124
            copy_all(self.weave_store, destination.weave_store)
125
            copy_all(self.revision_store, destination.revision_store)
126
        finally:
127
            destination.unlock()
1185.66.8 by Aaron Bentley
Applied Jelmer's patch to make clone a branch operation
128
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
129
    def has_revision(self, revision_id):
130
        """True if this branch has a copy of the revision.
131
132
        This does not necessarily imply the revision is merge
133
        or on the mainline."""
134
        return (revision_id is None
135
                or self.revision_store.has_id(revision_id))
136
137
    @needs_read_lock
138
    def get_revision_xml_file(self, revision_id):
139
        """Return XML file object for revision object."""
140
        if not revision_id or not isinstance(revision_id, basestring):
141
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
142
        try:
143
            return self.revision_store.get(revision_id)
144
        except (IndexError, KeyError):
145
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
146
1185.65.27 by Robert Collins
Tweak storage towards mergability.
147
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
148
    def get_revision_xml(self, revision_id):
149
        return self.get_revision_xml_file(revision_id).read()
150
1185.65.27 by Robert Collins
Tweak storage towards mergability.
151
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
152
    def get_revision(self, revision_id):
153
        """Return the Revision object for a named revision"""
154
        xml_file = self.get_revision_xml_file(revision_id)
155
156
        try:
157
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
158
        except SyntaxError, e:
159
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
160
                                         [revision_id,
161
                                          str(e)])
162
            
163
        assert r.revision_id == revision_id
164
        return r
165
1185.65.27 by Robert Collins
Tweak storage towards mergability.
166
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
167
    def get_revision_sha1(self, revision_id):
168
        """Hash the stored value of a revision, and return it."""
169
        # In the future, revision entries will be signed. At that
170
        # point, it is probably best *not* to include the signature
171
        # in the revision hash. Because that lets you re-sign
172
        # the revision, (add signatures/remove signatures) and still
173
        # have all hash pointers stay consistent.
174
        # But for now, just hash the contents.
175
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
176
177
    @needs_write_lock
178
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
179
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
180
                                revision_id, "sig")
181
1185.65.27 by Robert Collins
Tweak storage towards mergability.
182
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
183
    def get_inventory_weave(self):
184
        return self.control_weaves.get_weave('inventory',
185
            self.get_transaction())
186
1185.65.27 by Robert Collins
Tweak storage towards mergability.
187
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
188
    def get_inventory(self, revision_id):
189
        """Get Inventory object by hash."""
190
        xml = self.get_inventory_xml(revision_id)
191
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
192
1185.65.27 by Robert Collins
Tweak storage towards mergability.
193
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
194
    def get_inventory_xml(self, revision_id):
195
        """Get inventory XML as a file object."""
196
        try:
197
            assert isinstance(revision_id, basestring), type(revision_id)
198
            iw = self.get_inventory_weave()
199
            return iw.get_text(iw.lookup(revision_id))
200
        except IndexError:
201
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
202
1185.65.27 by Robert Collins
Tweak storage towards mergability.
203
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
204
    def get_inventory_sha1(self, revision_id):
205
        """Return the sha1 hash of the inventory entry
206
        """
207
        return self.get_revision(revision_id).inventory_sha1
208
1185.65.27 by Robert Collins
Tweak storage towards mergability.
209
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
210
    def get_revision_inventory(self, revision_id):
211
        """Return inventory of a past revision."""
212
        # TODO: Unify this with get_inventory()
213
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
214
        # must be the same as its revision, so this is trivial.
1534.4.28 by Robert Collins
first cut at merge from integration.
215
        if revision_id is None:
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
216
            # This does not make sense: if there is no revision,
217
            # then it is the current tree inventory surely ?!
218
            # and thus get_root_id() is something that looks at the last
219
            # commit on the branch, and the get_root_id is an inventory check.
220
            raise NotImplementedError
221
            # return Inventory(self.get_root_id())
222
        else:
223
            return self.get_inventory(revision_id)
224
1185.65.27 by Robert Collins
Tweak storage towards mergability.
225
    @needs_read_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
226
    def revision_tree(self, revision_id):
227
        """Return Tree for a revision on this branch.
228
229
        `revision_id` may be None for the null revision, in which case
230
        an `EmptyTree` is returned."""
231
        # TODO: refactor this to use an existing revision object
232
        # so we don't need to read it in twice.
1534.4.28 by Robert Collins
first cut at merge from integration.
233
        if revision_id is None or revision_id == NULL_REVISION:
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
234
            return EmptyTree()
235
        else:
236
            inv = self.get_revision_inventory(revision_id)
1185.65.17 by Robert Collins
Merge from integration, mode-changes are broken.
237
            return RevisionTree(self, inv, revision_id)
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
238
1185.65.27 by Robert Collins
Tweak storage towards mergability.
239
    @needs_read_lock
1185.66.2 by Aaron Bentley
Moved get_ancestry to RevisionStorage
240
    def get_ancestry(self, revision_id):
241
        """Return a list of revision-ids integrated by a revision.
242
        
243
        This is topologically sorted.
244
        """
245
        if revision_id is None:
246
            return [None]
247
        w = self.get_inventory_weave()
248
        return [None] + map(w.idx_to_name,
249
                            w.inclusions([w.lookup(revision_id)]))
250
1185.65.4 by Aaron Bentley
Fixed cat command
251
    @needs_read_lock
252
    def print_file(self, file, revision_id):
1185.65.29 by Robert Collins
Implement final review suggestions.
253
        """Print `file` to stdout.
254
        
255
        FIXME RBC 20060125 as John Meinel points out this is a bad api
256
        - it writes to stdout, it assumes that that is valid etc. Fix
257
        by creating a new more flexible convenience function.
258
        """
1185.65.4 by Aaron Bentley
Fixed cat command
259
        tree = self.revision_tree(revision_id)
260
        # use inventory as it was in that revision
261
        file_id = tree.inventory.path2id(file)
262
        if not file_id:
263
            raise BzrError("%r is not present in revision %s" % (file, revno))
1185.65.15 by Robert Collins
Merge from integration.
264
            try:
265
                revno = self.revision_id_to_revno(revision_id)
266
            except errors.NoSuchRevision:
267
                # TODO: This should not be BzrError,
268
                # but NoSuchFile doesn't fit either
269
                raise BzrError('%r is not present in revision %s' 
270
                                % (file, revision_id))
271
            else:
272
                raise BzrError('%r is not present in revision %s'
273
                                % (file, revno))
1185.65.4 by Aaron Bentley
Fixed cat command
274
        tree.print_file(file_id)
275
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
276
    def get_transaction(self):
277
        return self.control_files.get_transaction()
278
1185.65.27 by Robert Collins
Tweak storage towards mergability.
279
    @needs_write_lock
1185.65.1 by Aaron Bentley
Refactored out ControlFiles and RevisionStore from _Branch
280
    def sign_revision(self, revision_id, gpg_strategy):
281
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
282
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)