~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

MergeĀ fromĀ jam-storage.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
16
 
 
17
from cStringIO import StringIO
 
18
 
 
19
from bzrlib.lockable_files import LockableFiles
 
20
from bzrlib.tree import EmptyTree
 
21
from bzrlib.revision import NULL_REVISION
 
22
from bzrlib.store import copy_all
 
23
from bzrlib.store.weave import WeaveStore
 
24
from bzrlib.store.text import TextStore
 
25
import bzrlib.xml5
 
26
from bzrlib.tree import RevisionTree
 
27
from bzrlib.errors import InvalidRevisionId
 
28
from bzrlib.testament import Testament
 
29
 
 
30
 
 
31
def needs_read_lock(unbound):
 
32
    """Decorate unbound to take out and release a read lock."""
 
33
    def decorated(self, *args, **kwargs):
 
34
        self.control_files.lock_read()
 
35
        try:
 
36
            return unbound(self, *args, **kwargs)
 
37
        finally:
 
38
            self.control_files.unlock()
 
39
    return decorated
 
40
 
 
41
 
 
42
def needs_write_lock(unbound):
 
43
    """Decorate unbound to take out and release a write lock."""
 
44
    def decorated(self, *args, **kwargs):
 
45
        self.control_files.lock_write()
 
46
        try:
 
47
            return unbound(self, *args, **kwargs)
 
48
        finally:
 
49
            self.control_files.unlock()
 
50
    return decorated
 
51
 
 
52
 
 
53
class Repository(object):
 
54
 
 
55
    def __init__(self, transport, branch_format):
 
56
        object.__init__(self)
 
57
        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
 
58
 
 
59
        dir_mode = self.control_files._dir_mode
 
60
        file_mode = self.control_files._file_mode
 
61
 
 
62
        def get_weave(name, prefixed=False):
 
63
            if name:
 
64
                name = bzrlib.BZRDIR + '/' + unicode(name)
 
65
            else:
 
66
                name = bzrlib.BZRDIR
 
67
            relpath = self.control_files._escape(name)
 
68
            weave_transport = transport.clone(relpath)
 
69
            ws = WeaveStore(weave_transport, prefixed=prefixed,
 
70
                            dir_mode=dir_mode,
 
71
                            file_mode=file_mode)
 
72
            if self.control_files._transport.should_cache():
 
73
                ws.enable_cache = True
 
74
            return ws
 
75
 
 
76
        def get_store(name, compressed=True, prefixed=False):
 
77
            # FIXME: This approach of assuming stores are all entirely compressed
 
78
            # or entirely uncompressed is tidy, but breaks upgrade from 
 
79
            # some existing branches where there's a mixture; we probably 
 
80
            # still want the option to look for both.
 
81
            if name:
 
82
                name = bzrlib.BZRDIR + '/' + unicode(name)
 
83
            else:
 
84
                name = bzrlib.BZRDIR
 
85
            relpath = self.control_files._escape(name)
 
86
            store = TextStore(transport.clone(relpath),
 
87
                              prefixed=prefixed, compressed=compressed,
 
88
                              dir_mode=dir_mode,
 
89
                              file_mode=file_mode)
 
90
            #if self._transport.should_cache():
 
91
            #    cache_path = os.path.join(self.cache_root, name)
 
92
            #    os.mkdir(cache_path)
 
93
            #    store = bzrlib.store.CachedStore(store, cache_path)
 
94
            return store
 
95
 
 
96
        if branch_format == 4:
 
97
            self.inventory_store = get_store('inventory-store')
 
98
            self.text_store = get_store('text-store')
 
99
            self.revision_store = get_store('revision-store')
 
100
        elif branch_format == 5:
 
101
            self.control_weaves = get_weave('')
 
102
            self.weave_store = get_weave('weaves')
 
103
            self.revision_store = get_store('revision-store', compressed=False)
 
104
        elif branch_format == 6:
 
105
            self.control_weaves = get_weave('')
 
106
            self.weave_store = get_weave('weaves', prefixed=True)
 
107
            self.revision_store = get_store('revision-store', compressed=False,
 
108
                                            prefixed=True)
 
109
        self.revision_store.register_suffix('sig')
 
110
 
 
111
    def lock_write(self):
 
112
        self.control_files.lock_write()
 
113
 
 
114
    def lock_read(self):
 
115
        self.control_files.lock_read()
 
116
 
 
117
    def unlock(self):
 
118
        self.control_files.unlock()
 
119
 
 
120
    def copy(self, destination):
 
121
        destination.control_weaves.copy_multi(self.control_weaves, 
 
122
                ['inventory'])
 
123
        copy_all(self.weave_store, destination.weave_store)
 
124
        copy_all(self.revision_store, destination.revision_store)
 
125
 
 
126
    def has_revision(self, revision_id):
 
127
        """True if this branch has a copy of the revision.
 
128
 
 
129
        This does not necessarily imply the revision is merge
 
130
        or on the mainline."""
 
131
        return (revision_id is None
 
132
                or self.revision_store.has_id(revision_id))
 
133
 
 
134
    @needs_read_lock
 
135
    def get_revision_xml_file(self, revision_id):
 
136
        """Return XML file object for revision object."""
 
137
        if not revision_id or not isinstance(revision_id, basestring):
 
138
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
 
139
        try:
 
140
            return self.revision_store.get(revision_id)
 
141
        except (IndexError, KeyError):
 
142
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
143
 
 
144
    def get_revision_xml(self, revision_id):
 
145
        return self.get_revision_xml_file(revision_id).read()
 
146
 
 
147
    def get_revision(self, revision_id):
 
148
        """Return the Revision object for a named revision"""
 
149
        xml_file = self.get_revision_xml_file(revision_id)
 
150
 
 
151
        try:
 
152
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
153
        except SyntaxError, e:
 
154
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
155
                                         [revision_id,
 
156
                                          str(e)])
 
157
            
 
158
        assert r.revision_id == revision_id
 
159
        return r
 
160
 
 
161
    def get_revision_sha1(self, revision_id):
 
162
        """Hash the stored value of a revision, and return it."""
 
163
        # In the future, revision entries will be signed. At that
 
164
        # point, it is probably best *not* to include the signature
 
165
        # in the revision hash. Because that lets you re-sign
 
166
        # the revision, (add signatures/remove signatures) and still
 
167
        # have all hash pointers stay consistent.
 
168
        # But for now, just hash the contents.
 
169
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
 
170
 
 
171
    @needs_write_lock
 
172
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
173
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
 
174
                                revision_id, "sig")
 
175
 
 
176
    def get_inventory_weave(self):
 
177
        return self.control_weaves.get_weave('inventory',
 
178
            self.get_transaction())
 
179
 
 
180
    def get_inventory(self, revision_id):
 
181
        """Get Inventory object by hash."""
 
182
        xml = self.get_inventory_xml(revision_id)
 
183
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
184
 
 
185
    def get_inventory_xml(self, revision_id):
 
186
        """Get inventory XML as a file object."""
 
187
        try:
 
188
            assert isinstance(revision_id, basestring), type(revision_id)
 
189
            iw = self.get_inventory_weave()
 
190
            return iw.get_text(iw.lookup(revision_id))
 
191
        except IndexError:
 
192
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
193
 
 
194
    def get_inventory_sha1(self, revision_id):
 
195
        """Return the sha1 hash of the inventory entry
 
196
        """
 
197
        return self.get_revision(revision_id).inventory_sha1
 
198
 
 
199
    def get_revision_inventory(self, revision_id):
 
200
        """Return inventory of a past revision."""
 
201
        # TODO: Unify this with get_inventory()
 
202
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
203
        # must be the same as its revision, so this is trivial.
 
204
        if revision_id == None:
 
205
            # This does not make sense: if there is no revision,
 
206
            # then it is the current tree inventory surely ?!
 
207
            # and thus get_root_id() is something that looks at the last
 
208
            # commit on the branch, and the get_root_id is an inventory check.
 
209
            raise NotImplementedError
 
210
            # return Inventory(self.get_root_id())
 
211
        else:
 
212
            return self.get_inventory(revision_id)
 
213
 
 
214
    def revision_tree(self, revision_id):
 
215
        """Return Tree for a revision on this branch.
 
216
 
 
217
        `revision_id` may be None for the null revision, in which case
 
218
        an `EmptyTree` is returned."""
 
219
        # TODO: refactor this to use an existing revision object
 
220
        # so we don't need to read it in twice.
 
221
        if revision_id == None or revision_id == NULL_REVISION:
 
222
            return EmptyTree()
 
223
        else:
 
224
            inv = self.get_revision_inventory(revision_id)
 
225
            return RevisionTree(self, inv, revision_id)
 
226
 
 
227
    def get_ancestry(self, revision_id):
 
228
        """Return a list of revision-ids integrated by a revision.
 
229
        
 
230
        This is topologically sorted.
 
231
        """
 
232
        if revision_id is None:
 
233
            return [None]
 
234
        w = self.get_inventory_weave()
 
235
        return [None] + map(w.idx_to_name,
 
236
                            w.inclusions([w.lookup(revision_id)]))
 
237
 
 
238
    @needs_read_lock
 
239
    def print_file(self, file, revision_id):
 
240
        """Print `file` to stdout."""
 
241
        tree = self.revision_tree(revision_id)
 
242
        # use inventory as it was in that revision
 
243
        file_id = tree.inventory.path2id(file)
 
244
        if not file_id:
 
245
            raise BzrError("%r is not present in revision %s" % (file, revno))
 
246
            try:
 
247
                revno = self.revision_id_to_revno(revision_id)
 
248
            except errors.NoSuchRevision:
 
249
                # TODO: This should not be BzrError,
 
250
                # but NoSuchFile doesn't fit either
 
251
                raise BzrError('%r is not present in revision %s' 
 
252
                                % (file, revision_id))
 
253
            else:
 
254
                raise BzrError('%r is not present in revision %s'
 
255
                                % (file, revno))
 
256
        tree.print_file(file_id)
 
257
 
 
258
    def get_transaction(self):
 
259
        return self.control_files.get_transaction()
 
260
 
 
261
    def sign_revision(self, revision_id, gpg_strategy):
 
262
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
263
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)