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
29
from bzrlib.decorators import needs_read_lock, needs_write_lock
33
class Repository(object):
34
"""Repository holding history for one or more branches.
36
The repository holds and retrieves historical information including
37
revisions and file history. It's normally accessed only by the Branch,
38
which views a particular line of development through that history.
40
The Repository builds on top of Stores and a Transport, which respectively
41
describe the disk data format and the way of accessing the (possibly
45
def __init__(self, transport, branch_format):
47
self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
49
dir_mode = self.control_files._dir_mode
50
file_mode = self.control_files._file_mode
52
def get_weave(name, prefixed=False):
54
name = bzrlib.BZRDIR + '/' + unicode(name)
57
relpath = self.control_files._escape(name)
58
weave_transport = transport.clone(relpath)
59
ws = WeaveStore(weave_transport, prefixed=prefixed,
62
if self.control_files._transport.should_cache():
63
ws.enable_cache = True
66
def get_store(name, compressed=True, prefixed=False):
67
# FIXME: This approach of assuming stores are all entirely compressed
68
# or entirely uncompressed is tidy, but breaks upgrade from
69
# some existing branches where there's a mixture; we probably
70
# still want the option to look for both.
72
name = bzrlib.BZRDIR + '/' + unicode(name)
75
relpath = self.control_files._escape(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()
111
def copy(self, destination):
112
destination.lock_write()
114
destination.control_weaves.copy_multi(self.control_weaves,
116
copy_all(self.weave_store, destination.weave_store)
117
copy_all(self.revision_store, destination.revision_store)
121
def has_revision(self, revision_id):
122
"""True if this branch has a copy of the revision.
124
This does not necessarily imply the revision is merge
125
or on the mainline."""
126
return (revision_id is None
127
or self.revision_store.has_id(revision_id))
130
def get_revision_xml_file(self, revision_id):
131
"""Return XML file object for revision object."""
132
if not revision_id or not isinstance(revision_id, basestring):
133
raise InvalidRevisionId(revision_id=revision_id, branch=self)
135
return self.revision_store.get(revision_id)
136
except (IndexError, KeyError):
137
raise bzrlib.errors.NoSuchRevision(self, revision_id)
140
def get_revision_xml(self, revision_id):
141
return self.get_revision_xml_file(revision_id).read()
144
def get_revision(self, revision_id):
145
"""Return the Revision object for a named revision"""
146
xml_file = self.get_revision_xml_file(revision_id)
149
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
150
except SyntaxError, e:
151
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
155
assert r.revision_id == revision_id
159
def get_revision_sha1(self, revision_id):
160
"""Hash the stored value of a revision, and return it."""
161
# In the future, revision entries will be signed. At that
162
# point, it is probably best *not* to include the signature
163
# in the revision hash. Because that lets you re-sign
164
# the revision, (add signatures/remove signatures) and still
165
# have all hash pointers stay consistent.
166
# But for now, just hash the contents.
167
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
170
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
171
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
175
def get_inventory_weave(self):
176
return self.control_weaves.get_weave('inventory',
177
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)
186
def get_inventory_xml(self, revision_id):
187
"""Get inventory XML as a file object."""
189
assert isinstance(revision_id, basestring), type(revision_id)
190
iw = self.get_inventory_weave()
191
return iw.get_text(iw.lookup(revision_id))
193
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
196
def get_inventory_sha1(self, revision_id):
197
"""Return the sha1 hash of the inventory entry
199
return self.get_revision(revision_id).inventory_sha1
202
def get_revision_inventory(self, revision_id):
203
"""Return inventory of a past revision."""
204
# TODO: Unify this with get_inventory()
205
# bzr 0.0.6 and later imposes the constraint that the inventory_id
206
# must be the same as its revision, so this is trivial.
207
if revision_id == None:
208
# This does not make sense: if there is no revision,
209
# then it is the current tree inventory surely ?!
210
# and thus get_root_id() is something that looks at the last
211
# commit on the branch, and the get_root_id is an inventory check.
212
raise NotImplementedError
213
# return Inventory(self.get_root_id())
215
return self.get_inventory(revision_id)
218
def revision_tree(self, revision_id):
219
"""Return Tree for a revision on this branch.
221
`revision_id` may be None for the null revision, in which case
222
an `EmptyTree` is returned."""
223
# TODO: refactor this to use an existing revision object
224
# so we don't need to read it in twice.
225
if revision_id == None or revision_id == NULL_REVISION:
228
inv = self.get_revision_inventory(revision_id)
229
return RevisionTree(self, inv, revision_id)
232
def get_ancestry(self, revision_id):
233
"""Return a list of revision-ids integrated by a revision.
235
This is topologically sorted.
237
if revision_id is None:
239
w = self.get_inventory_weave()
240
return [None] + map(w.idx_to_name,
241
w.inclusions([w.lookup(revision_id)]))
244
def print_file(self, file, revision_id):
245
"""Print `file` to stdout.
247
FIXME RBC 20060125 as John Meinel points out this is a bad api
248
- it writes to stdout, it assumes that that is valid etc. Fix
249
by creating a new more flexible convenience function.
251
tree = self.revision_tree(revision_id)
252
# use inventory as it was in that revision
253
file_id = tree.inventory.path2id(file)
255
raise BzrError("%r is not present in revision %s" % (file, revno))
257
revno = self.revision_id_to_revno(revision_id)
258
except errors.NoSuchRevision:
259
# TODO: This should not be BzrError,
260
# but NoSuchFile doesn't fit either
261
raise BzrError('%r is not present in revision %s'
262
% (file, revision_id))
264
raise BzrError('%r is not present in revision %s'
266
tree.print_file(file_id)
268
def get_transaction(self):
269
return self.control_files.get_transaction()
272
def sign_revision(self, revision_id, gpg_strategy):
273
plaintext = Testament.from_revision(self, revision_id).as_short_text()
274
self.store_revision_signature(gpg_strategy, plaintext, revision_id)