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.decorators import needs_read_lock, needs_write_lock
20
from bzrlib.errors import InvalidRevisionId
21
from bzrlib.lockable_files import LockableFiles
22
from bzrlib.osutils import safe_unicode
23
from bzrlib.revision import NULL_REVISION
24
from bzrlib.store import copy_all
25
from bzrlib.store.weave import WeaveStore
26
from bzrlib.store.text import TextStore
27
from bzrlib.tree import RevisionTree
28
from bzrlib.testament import Testament
29
from bzrlib.tree import EmptyTree
34
class Repository(object):
35
"""Repository holding history for one or more branches.
37
The repository holds and retrieves historical information including
38
revisions and file history. It's normally accessed only by the Branch,
39
which views a particular line of development through that history.
41
The Repository builds on top of Stores and a Transport, which respectively
42
describe the disk data format and the way of accessing the (possibly
46
def __init__(self, transport, branch_format):
47
# circular dependencies:
48
from bzrlib.branch import (BzrBranchFormat4,
53
self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'README')
55
dir_mode = self.control_files._dir_mode
56
file_mode = self.control_files._file_mode
58
def get_weave(name, prefixed=False):
60
name = bzrlib.BZRDIR + '/' + safe_unicode(name)
63
relpath = self.control_files._escape(name)
64
weave_transport = transport.clone(relpath)
65
ws = WeaveStore(weave_transport, prefixed=prefixed,
68
if self.control_files._transport.should_cache():
69
ws.enable_cache = True
73
def get_store(name, compressed=True, prefixed=False):
74
# FIXME: This approach of assuming stores are all entirely compressed
75
# or entirely uncompressed is tidy, but breaks upgrade from
76
# some existing branches where there's a mixture; we probably
77
# still want the option to look for both.
79
name = bzrlib.BZRDIR + '/' + safe_unicode(name)
82
relpath = self.control_files._escape(name)
83
store = TextStore(transport.clone(relpath),
84
prefixed=prefixed, compressed=compressed,
87
#if self._transport.should_cache():
88
# cache_path = os.path.join(self.cache_root, name)
89
# os.mkdir(cache_path)
90
# store = bzrlib.store.CachedStore(store, cache_path)
94
if isinstance(branch_format, BzrBranchFormat4):
95
self.inventory_store = get_store('inventory-store')
96
self.text_store = get_store('text-store')
97
self.revision_store = get_store('revision-store')
98
elif isinstance(branch_format, BzrBranchFormat5):
99
self.control_weaves = get_weave('')
100
self.weave_store = get_weave('weaves')
101
self.revision_store = get_store('revision-store', compressed=False)
102
elif isinstance(branch_format, BzrBranchFormat6):
103
self.control_weaves = get_weave('')
104
self.weave_store = get_weave('weaves', prefixed=True)
105
self.revision_store = get_store('revision-store', compressed=False,
107
self.revision_store.register_suffix('sig')
109
def lock_write(self):
110
self.control_files.lock_write()
113
self.control_files.lock_read()
116
self.control_files.unlock()
119
def copy(self, destination):
120
destination.lock_write()
122
destination.control_weaves.copy_multi(self.control_weaves,
124
copy_all(self.weave_store, destination.weave_store)
125
copy_all(self.revision_store, destination.revision_store)
129
def has_revision(self, revision_id):
130
"""True if this branch has a copy of the revision.
132
This does not necessarily imply the revision is merge
133
or on the mainline."""
134
return (revision_id is None
135
or self.revision_store.has_id(revision_id))
138
def get_revision_xml_file(self, revision_id):
139
"""Return XML file object for revision object."""
140
if not revision_id or not isinstance(revision_id, basestring):
141
raise InvalidRevisionId(revision_id=revision_id, branch=self)
143
return self.revision_store.get(revision_id)
144
except (IndexError, KeyError):
145
raise bzrlib.errors.NoSuchRevision(self, revision_id)
148
def get_revision_xml(self, revision_id):
149
return self.get_revision_xml_file(revision_id).read()
152
def get_revision(self, revision_id):
153
"""Return the Revision object for a named revision"""
154
xml_file = self.get_revision_xml_file(revision_id)
157
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
158
except SyntaxError, e:
159
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
163
assert r.revision_id == revision_id
167
def get_revision_sha1(self, revision_id):
168
"""Hash the stored value of a revision, and return it."""
169
# In the future, revision entries will be signed. At that
170
# point, it is probably best *not* to include the signature
171
# in the revision hash. Because that lets you re-sign
172
# the revision, (add signatures/remove signatures) and still
173
# have all hash pointers stay consistent.
174
# But for now, just hash the contents.
175
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
178
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
179
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
183
def get_inventory_weave(self):
184
return self.control_weaves.get_weave('inventory',
185
self.get_transaction())
188
def get_inventory(self, revision_id):
189
"""Get Inventory object by hash."""
190
xml = self.get_inventory_xml(revision_id)
191
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
194
def get_inventory_xml(self, revision_id):
195
"""Get inventory XML as a file object."""
197
assert isinstance(revision_id, basestring), type(revision_id)
198
iw = self.get_inventory_weave()
199
return iw.get_text(iw.lookup(revision_id))
201
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
204
def get_inventory_sha1(self, revision_id):
205
"""Return the sha1 hash of the inventory entry
207
return self.get_revision(revision_id).inventory_sha1
210
def get_revision_inventory(self, revision_id):
211
"""Return inventory of a past revision."""
212
# TODO: Unify this with get_inventory()
213
# bzr 0.0.6 and later imposes the constraint that the inventory_id
214
# must be the same as its revision, so this is trivial.
215
if revision_id is None:
216
# This does not make sense: if there is no revision,
217
# then it is the current tree inventory surely ?!
218
# and thus get_root_id() is something that looks at the last
219
# commit on the branch, and the get_root_id is an inventory check.
220
raise NotImplementedError
221
# return Inventory(self.get_root_id())
223
return self.get_inventory(revision_id)
226
def revision_tree(self, revision_id):
227
"""Return Tree for a revision on this branch.
229
`revision_id` may be None for the null revision, in which case
230
an `EmptyTree` is returned."""
231
# TODO: refactor this to use an existing revision object
232
# so we don't need to read it in twice.
233
if revision_id is None or revision_id == NULL_REVISION:
236
inv = self.get_revision_inventory(revision_id)
237
return RevisionTree(self, inv, revision_id)
240
def get_ancestry(self, revision_id):
241
"""Return a list of revision-ids integrated by a revision.
243
This is topologically sorted.
245
if revision_id is None:
247
w = self.get_inventory_weave()
248
return [None] + map(w.idx_to_name,
249
w.inclusions([w.lookup(revision_id)]))
252
def print_file(self, file, revision_id):
253
"""Print `file` to stdout.
255
FIXME RBC 20060125 as John Meinel points out this is a bad api
256
- it writes to stdout, it assumes that that is valid etc. Fix
257
by creating a new more flexible convenience function.
259
tree = self.revision_tree(revision_id)
260
# use inventory as it was in that revision
261
file_id = tree.inventory.path2id(file)
263
raise BzrError("%r is not present in revision %s" % (file, revno))
265
revno = self.revision_id_to_revno(revision_id)
266
except errors.NoSuchRevision:
267
# TODO: This should not be BzrError,
268
# but NoSuchFile doesn't fit either
269
raise BzrError('%r is not present in revision %s'
270
% (file, revision_id))
272
raise BzrError('%r is not present in revision %s'
274
tree.print_file(file_id)
276
def get_transaction(self):
277
return self.control_files.get_transaction()
280
def sign_revision(self, revision_id, gpg_strategy):
281
plaintext = Testament.from_revision(self, revision_id).as_short_text()
282
self.store_revision_signature(gpg_strategy, plaintext, revision_id)