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,
56
dir_mode=None, file_mode=None):
58
self.control_files = LockableFiles(transport, bzrlib.BZRDIR, 'README')
59
def get_weave(name, prefixed=False):
60
relpath = self.control_files._rel_controlfilename(unicode(name))
61
weave_transport = transport.clone(relpath)
62
ws = WeaveStore(weave_transport, prefixed=prefixed,
65
if self.control_files._transport.should_cache():
66
ws.enable_cache = True
69
def get_store(name, compressed=True, prefixed=False):
70
# FIXME: This approach of assuming stores are all entirely compressed
71
# or entirely uncompressed is tidy, but breaks upgrade from
72
# some existing branches where there's a mixture; we probably
73
# still want the option to look for both.
75
relpath = self.control_files._rel_controlfilename(name)
76
store = TextStore(transport.clone(relpath),
77
prefixed=prefixed, compressed=compressed,
80
#if self._transport.should_cache():
81
# cache_path = os.path.join(self.cache_root, name)
82
# os.mkdir(cache_path)
83
# store = bzrlib.store.CachedStore(store, cache_path)
86
if branch_format == 4:
87
self.inventory_store = get_store('inventory-store')
88
self.text_store = get_store('text-store')
89
self.revision_store = get_store('revision-store')
90
elif branch_format == 5:
91
self.control_weaves = get_weave('')
92
self.weave_store = get_weave('weaves')
93
self.revision_store = get_store('revision-store', compressed=False)
94
elif branch_format == 6:
95
self.control_weaves = get_weave('')
96
self.weave_store = get_weave('weaves', prefixed=True)
97
self.revision_store = get_store('revision-store', compressed=False,
99
self.revision_store.register_suffix('sig')
101
def lock_write(self):
102
self.control_files.lock_write()
105
self.control_files.lock_read()
108
self.control_files.unlock()
110
def copy(self, destination):
111
destination.control_weaves.copy_multi(self.control_weaves,
113
copy_all(self.weave_store, destination.weave_store)
114
copy_all(self.revision_store, destination.revision_store)
116
def has_revision(self, revision_id):
117
"""True if this branch has a copy of the revision.
119
This does not necessarily imply the revision is merge
120
or on the mainline."""
121
return (revision_id is None
122
or self.revision_store.has_id(revision_id))
125
def get_revision_xml_file(self, revision_id):
126
"""Return XML file object for revision object."""
127
if not revision_id or not isinstance(revision_id, basestring):
128
raise InvalidRevisionId(revision_id=revision_id, branch=self)
130
return self.revision_store.get(revision_id)
131
except (IndexError, KeyError):
132
raise bzrlib.errors.NoSuchRevision(self, revision_id)
134
def get_revision_xml(self, revision_id):
135
return self.get_revision_xml_file(revision_id).read()
137
def get_revision(self, revision_id):
138
"""Return the Revision object for a named revision"""
139
xml_file = self.get_revision_xml_file(revision_id)
142
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
143
except SyntaxError, e:
144
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
148
assert r.revision_id == revision_id
151
def get_revision_sha1(self, revision_id):
152
"""Hash the stored value of a revision, and return it."""
153
# In the future, revision entries will be signed. At that
154
# point, it is probably best *not* to include the signature
155
# in the revision hash. Because that lets you re-sign
156
# the revision, (add signatures/remove signatures) and still
157
# have all hash pointers stay consistent.
158
# But for now, just hash the contents.
159
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
162
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
163
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
166
def get_inventory_weave(self):
167
return self.control_weaves.get_weave('inventory',
168
self.get_transaction())
170
def get_inventory(self, revision_id):
171
"""Get Inventory object by hash."""
172
xml = self.get_inventory_xml(revision_id)
173
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
175
def get_inventory_xml(self, revision_id):
176
"""Get inventory XML as a file object."""
178
assert isinstance(revision_id, basestring), type(revision_id)
179
iw = self.get_inventory_weave()
180
return iw.get_text(iw.lookup(revision_id))
182
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
184
def get_inventory_sha1(self, revision_id):
185
"""Return the sha1 hash of the inventory entry
187
return self.get_revision(revision_id).inventory_sha1
189
def get_revision_inventory(self, revision_id):
190
"""Return inventory of a past revision."""
191
# TODO: Unify this with get_inventory()
192
# bzr 0.0.6 and later imposes the constraint that the inventory_id
193
# must be the same as its revision, so this is trivial.
194
if revision_id == None:
195
# This does not make sense: if there is no revision,
196
# then it is the current tree inventory surely ?!
197
# and thus get_root_id() is something that looks at the last
198
# commit on the branch, and the get_root_id is an inventory check.
199
raise NotImplementedError
200
# return Inventory(self.get_root_id())
202
return self.get_inventory(revision_id)
204
def revision_tree(self, revision_id):
205
"""Return Tree for a revision on this branch.
207
`revision_id` may be None for the null revision, in which case
208
an `EmptyTree` is returned."""
209
# TODO: refactor this to use an existing revision object
210
# so we don't need to read it in twice.
211
if revision_id == None or revision_id == NULL_REVISION:
214
inv = self.get_revision_inventory(revision_id)
215
return RevisionTree(self, inv, revision_id)
217
def get_ancestry(self, revision_id):
218
"""Return a list of revision-ids integrated by a revision.
220
This is topologically sorted.
222
if revision_id is None:
224
w = self.get_inventory_weave()
225
return [None] + map(w.idx_to_name,
226
w.inclusions([w.lookup(revision_id)]))
229
def print_file(self, file, revision_id):
230
"""Print `file` to stdout."""
231
tree = self.revision_tree(revision_id)
232
# use inventory as it was in that revision
233
file_id = tree.inventory.path2id(file)
235
raise BzrError("%r is not present in revision %s" % (file, revno))
237
revno = self.revision_id_to_revno(revision_id)
238
except errors.NoSuchRevision:
239
# TODO: This should not be BzrError,
240
# but NoSuchFile doesn't fit either
241
raise BzrError('%r is not present in revision %s'
242
% (file, revision_id))
244
raise BzrError('%r is not present in revision %s'
246
tree.print_file(file_id)
248
def get_transaction(self):
249
return self.control_files.get_transaction()
251
def sign_revision(self, revision_id, gpg_strategy):
252
plaintext = Testament.from_revision(self, revision_id).as_short_text()
253
self.store_revision_signature(gpg_strategy, plaintext, revision_id)