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):
55
def __init__(self, transport, branch_format):
57
self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
59
dir_mode = self.control_files._dir_mode
60
file_mode = self.control_files._file_mode
62
def get_weave(name, prefixed=False):
64
name = bzrlib.BZRDIR + '/' + unicode(name)
67
relpath = self.control_files._escape(name)
68
weave_transport = transport.clone(relpath)
69
ws = WeaveStore(weave_transport, prefixed=prefixed,
72
if self.control_files._transport.should_cache():
73
ws.enable_cache = True
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.
82
name = bzrlib.BZRDIR + '/' + unicode(name)
85
relpath = self.control_files._escape(name)
86
store = TextStore(transport.clone(relpath),
87
prefixed=prefixed, compressed=compressed,
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)
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,
109
self.revision_store.register_suffix('sig')
111
def lock_write(self):
112
self.control_files.lock_write()
115
self.control_files.lock_read()
118
self.control_files.unlock()
120
def copy(self, destination):
121
destination.control_weaves.copy_multi(self.control_weaves,
123
copy_all(self.weave_store, destination.weave_store)
124
copy_all(self.revision_store, destination.revision_store)
126
def has_revision(self, revision_id):
127
"""True if this branch has a copy of the revision.
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))
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)
140
return self.revision_store.get(revision_id)
141
except (IndexError, KeyError):
142
raise bzrlib.errors.NoSuchRevision(self, revision_id)
144
def get_revision_xml(self, revision_id):
145
return self.get_revision_xml_file(revision_id).read()
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)
152
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
153
except SyntaxError, e:
154
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
158
assert r.revision_id == revision_id
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))
172
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
173
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
176
def get_inventory_weave(self):
177
return self.control_weaves.get_weave('inventory',
178
self.get_transaction())
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)
185
def get_inventory_xml(self, revision_id):
186
"""Get inventory XML as a file object."""
188
assert isinstance(revision_id, basestring), type(revision_id)
189
iw = self.get_inventory_weave()
190
return iw.get_text(iw.lookup(revision_id))
192
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
194
def get_inventory_sha1(self, revision_id):
195
"""Return the sha1 hash of the inventory entry
197
return self.get_revision(revision_id).inventory_sha1
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())
212
return self.get_inventory(revision_id)
214
def revision_tree(self, revision_id):
215
"""Return Tree for a revision on this branch.
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:
224
inv = self.get_revision_inventory(revision_id)
225
return RevisionTree(self, inv, revision_id)
227
def get_ancestry(self, revision_id):
228
"""Return a list of revision-ids integrated by a revision.
230
This is topologically sorted.
232
if revision_id is None:
234
w = self.get_inventory_weave()
235
return [None] + map(w.idx_to_name,
236
w.inclusions([w.lookup(revision_id)]))
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)
245
raise BzrError("%r is not present in revision %s" % (file, revno))
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))
254
raise BzrError('%r is not present in revision %s'
256
tree.print_file(file_id)
258
def get_transaction(self):
259
return self.control_files.get_transaction()
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)