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
17
from cStringIO import StringIO
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
26
from bzrlib.tree import RevisionTree
27
from bzrlib.errors import InvalidRevisionId
28
from bzrlib.testament import Testament
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()
36
return unbound(self, *args, **kwargs)
38
self.control_files.unlock()
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()
47
return unbound(self, *args, **kwargs)
49
self.control_files.unlock()
53
class Repository(object):
54
def __init__(self, transport, branch_format):
56
self.control_files = LockableFiles(transport, 'storage-lock')
57
def get_weave(name, prefixed=False):
58
relpath = self.control_files._rel_controlfilename(unicode(name))
59
weave_transport = self.control_files.make_transport(relpath)
60
ws = WeaveStore(weave_transport, prefixed=prefixed)
61
if self.control_files._transport.should_cache():
62
ws.enable_cache = True
65
def get_store(name, compressed=True, prefixed=False):
66
# FIXME: This approach of assuming stores are all entirely compressed
67
# or entirely uncompressed is tidy, but breaks upgrade from
68
# some existing branches where there's a mixture; we probably
69
# still want the option to look for both.
71
relpath = self.control_files._rel_controlfilename(name)
72
store = TextStore(self.control_files.make_transport(relpath),
73
prefixed=prefixed, compressed=compressed)
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 copy(self, destination):
105
destination.control_weaves.copy_multi(self.control_weaves,
107
copy_all(self.weave_store, destination.weave_store)
108
copy_all(self.revision_store, destination.revision_store)
110
def has_revision(self, revision_id):
111
"""True if this branch has a copy of the revision.
113
This does not necessarily imply the revision is merge
114
or on the mainline."""
115
return (revision_id is None
116
or self.revision_store.has_id(revision_id))
119
def get_revision_xml_file(self, revision_id):
120
"""Return XML file object for revision object."""
121
if not revision_id or not isinstance(revision_id, basestring):
122
raise InvalidRevisionId(revision_id=revision_id, branch=self)
124
return self.revision_store.get(revision_id)
125
except (IndexError, KeyError):
126
raise bzrlib.errors.NoSuchRevision(self, revision_id)
128
def get_revision_xml(self, revision_id):
129
return self.get_revision_xml_file(revision_id).read()
131
def get_revision(self, revision_id):
132
"""Return the Revision object for a named revision"""
133
xml_file = self.get_revision_xml_file(revision_id)
136
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
137
except SyntaxError, e:
138
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
142
assert r.revision_id == revision_id
145
def get_revision_sha1(self, revision_id):
146
"""Hash the stored value of a revision, and return it."""
147
# In the future, revision entries will be signed. At that
148
# point, it is probably best *not* to include the signature
149
# in the revision hash. Because that lets you re-sign
150
# the revision, (add signatures/remove signatures) and still
151
# have all hash pointers stay consistent.
152
# But for now, just hash the contents.
153
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
156
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
157
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
160
def get_inventory_weave(self):
161
return self.control_weaves.get_weave('inventory',
162
self.get_transaction())
164
def get_inventory(self, revision_id):
165
"""Get Inventory object by hash."""
166
xml = self.get_inventory_xml(revision_id)
167
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
169
def get_inventory_xml(self, revision_id):
170
"""Get inventory XML as a file object."""
172
assert isinstance(revision_id, basestring), type(revision_id)
173
iw = self.get_inventory_weave()
174
return iw.get_text(iw.lookup(revision_id))
176
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
178
def get_inventory_sha1(self, revision_id):
179
"""Return the sha1 hash of the inventory entry
181
return self.get_revision(revision_id).inventory_sha1
183
def get_revision_inventory(self, revision_id):
184
"""Return inventory of a past revision."""
185
# TODO: Unify this with get_inventory()
186
# bzr 0.0.6 and later imposes the constraint that the inventory_id
187
# must be the same as its revision, so this is trivial.
188
if revision_id == None:
189
# This does not make sense: if there is no revision,
190
# then it is the current tree inventory surely ?!
191
# and thus get_root_id() is something that looks at the last
192
# commit on the branch, and the get_root_id is an inventory check.
193
raise NotImplementedError
194
# return Inventory(self.get_root_id())
196
return self.get_inventory(revision_id)
198
def revision_tree(self, revision_id):
199
"""Return Tree for a revision on this branch.
201
`revision_id` may be None for the null revision, in which case
202
an `EmptyTree` is returned."""
203
# TODO: refactor this to use an existing revision object
204
# so we don't need to read it in twice.
205
if revision_id == None or revision_id == NULL_REVISION:
208
inv = self.get_revision_inventory(revision_id)
209
return RevisionTree(self.weave_store, inv, revision_id)
211
def get_ancestry(self, revision_id):
212
"""Return a list of revision-ids integrated by a revision.
214
This is topologically sorted.
216
if revision_id is None:
218
w = self.get_inventory_weave()
219
return [None] + map(w.idx_to_name,
220
w.inclusions([w.lookup(revision_id)]))
223
def print_file(self, file, revision_id):
224
"""Print `file` to stdout."""
225
tree = self.revision_tree(revision_id)
226
# use inventory as it was in that revision
227
file_id = tree.inventory.path2id(file)
229
raise BzrError("%r is not present in revision %s" % (file, revno))
231
revno = self.revision_id_to_revno(revision_id)
232
except errors.NoSuchRevision:
233
# TODO: This should not be BzrError,
234
# but NoSuchFile doesn't fit either
235
raise BzrError('%r is not present in revision %s'
236
% (file, revision_id))
238
raise BzrError('%r is not present in revision %s'
240
tree.print_file(file_id)
242
def get_transaction(self):
243
return self.control_files.get_transaction()
245
def sign_revision(self, revision_id, gpg_strategy):
246
plaintext = Testament.from_revision(self, revision_id).as_short_text()
247
self.store_revision_signature(gpg_strategy, plaintext, revision_id)