1
# Copyright (C) 2005 Canonical Ltd
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.
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.
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
from bzrlib.control_files import ControlFiles
17
from tree import EmptyTree
18
from bzrlib.revision import NULL_REVISION
19
from bzrlib.store.weave import WeaveStore
20
from bzrlib.store.compressed_text import CompressedTextStore
21
from bzrlib.store.text import TextStore
22
from cStringIO import StringIO
24
from bzrlib.tree import RevisionTree
25
from errors import InvalidRevisionId
26
from bzrlib.testament import Testament
28
def needs_read_lock(unbound):
29
"""Decorate unbound to take out and release a read lock."""
30
def decorated(self, *args, **kwargs):
31
self.control_files.lock_read()
33
return unbound(self, *args, **kwargs)
35
self.control_files.unlock()
39
def needs_write_lock(unbound):
40
"""Decorate unbound to take out and release a write lock."""
41
def decorated(self, *args, **kwargs):
42
self.control_files.lock_write()
44
return unbound(self, *args, **kwargs)
46
self.control_files.unlock()
49
class RevisionStorage(object):
50
def __init__(self, transport, branch_format):
52
self.control_files = ControlFiles(transport, 'storage-lock')
53
def get_weave(name, prefixed=False):
54
relpath = self.control_files._rel_controlfilename(name)
55
weave_transport = self.control_files.make_transport(relpath)
56
ws = WeaveStore(weave_transport, prefixed=prefixed)
57
if self.control_files._transport.should_cache():
58
ws.enable_cache = True
61
def get_store(name, compressed=True, prefixed=False):
62
# FIXME: This approach of assuming stores are all entirely compressed
63
# or entirely uncompressed is tidy, but breaks upgrade from
64
# some existing branches where there's a mixture; we probably
65
# still want the option to look for both.
66
relpath = self.control_files._rel_controlfilename(name)
68
store = CompressedTextStore(
69
self.control_files.make_transport(relpath),
72
store = TextStore(self.control_files.make_transport(relpath),
74
#if self._transport.should_cache():
75
# cache_path = os.path.join(self.cache_root, name)
76
# os.mkdir(cache_path)
77
# store = bzrlib.store.CachedStore(store, cache_path)
80
if branch_format == 4:
81
self.inventory_store = get_store('inventory-store')
82
self.text_store = get_store('text-store')
83
self.revision_store = get_store('revision-store')
84
elif branch_format == 5:
85
self.control_weaves = get_weave('')
86
self.weave_store = get_weave('weaves')
87
self.revision_store = get_store('revision-store', compressed=False)
88
elif branch_format == 6:
89
self.control_weaves = get_weave('')
90
self.weave_store = get_weave('weaves', prefixed=True)
91
self.revision_store = get_store('revision-store', compressed=False,
93
self.revision_store.register_suffix('sig')
96
self.control_files.lock_write()
99
self.control_files.lock_read()
102
self.control_files.unlock()
104
def has_revision(self, revision_id):
105
"""True if this branch has a copy of the revision.
107
This does not necessarily imply the revision is merge
108
or on the mainline."""
109
return (revision_id is None
110
or self.revision_store.has_id(revision_id))
113
def get_revision_xml_file(self, revision_id):
114
"""Return XML file object for revision object."""
115
if not revision_id or not isinstance(revision_id, basestring):
116
raise InvalidRevisionId(revision_id=revision_id, branch=self)
118
return self.revision_store.get(revision_id)
119
except (IndexError, KeyError):
120
raise bzrlib.errors.NoSuchRevision(self, revision_id)
123
get_revision_xml = get_revision_xml_file
125
def get_revision_xml(self, revision_id):
126
return self.get_revision_xml_file(revision_id).read()
128
def get_revision(self, revision_id):
129
"""Return the Revision object for a named revision"""
130
xml_file = self.get_revision_xml_file(revision_id)
133
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
134
except SyntaxError, e:
135
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
139
assert r.revision_id == revision_id
142
def get_revision_sha1(self, revision_id):
143
"""Hash the stored value of a revision, and return it."""
144
# In the future, revision entries will be signed. At that
145
# point, it is probably best *not* to include the signature
146
# in the revision hash. Because that lets you re-sign
147
# the revision, (add signatures/remove signatures) and still
148
# have all hash pointers stay consistent.
149
# But for now, just hash the contents.
150
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
153
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
154
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
157
def get_inventory_weave(self):
158
return self.control_weaves.get_weave('inventory',
159
self.get_transaction())
161
def get_inventory(self, revision_id):
162
"""Get Inventory object by hash."""
163
xml = self.get_inventory_xml(revision_id)
164
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
166
def get_inventory_xml(self, revision_id):
167
"""Get inventory XML as a file object."""
169
assert isinstance(revision_id, basestring), type(revision_id)
170
iw = self.get_inventory_weave()
171
return iw.get_text(iw.lookup(revision_id))
173
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
175
def get_inventory_sha1(self, revision_id):
176
"""Return the sha1 hash of the inventory entry
178
return self.get_revision(revision_id).inventory_sha1
180
def get_revision_inventory(self, revision_id):
181
"""Return inventory of a past revision."""
182
# TODO: Unify this with get_inventory()
183
# bzr 0.0.6 and later imposes the constraint that the inventory_id
184
# must be the same as its revision, so this is trivial.
185
if revision_id == None:
186
# This does not make sense: if there is no revision,
187
# then it is the current tree inventory surely ?!
188
# and thus get_root_id() is something that looks at the last
189
# commit on the branch, and the get_root_id is an inventory check.
190
raise NotImplementedError
191
# return Inventory(self.get_root_id())
193
return self.get_inventory(revision_id)
195
def revision_tree(self, revision_id):
196
"""Return Tree for a revision on this branch.
198
`revision_id` may be None for the null revision, in which case
199
an `EmptyTree` is returned."""
200
# TODO: refactor this to use an existing revision object
201
# so we don't need to read it in twice.
202
if revision_id == None or revision_id == NULL_REVISION:
205
inv = self.get_revision_inventory(revision_id)
206
return RevisionTree(self.weave_store, inv, revision_id)
208
def get_transaction(self):
209
return self.control_files.get_transaction()
211
def sign_revision(self, revision_id, gpg_strategy):
212
plaintext = Testament.from_revision(self, revision_id).as_short_text()
213
self.store_revision_signature(gpg_strategy, plaintext, revision_id)