14
16
# along with this program; if not, write to the Free Software
15
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# XXX: Some consideration of the problems that might occur if there are
18
# files whose id differs only in case. That should probably be forbidden.
19
# Author: Martin Pool <mbp@canonical.com>
23
from cStringIO import StringIO
26
from bzrlib.weavefile import read_weave, write_weave_v5
27
from bzrlib.weave import WeaveFile
28
from bzrlib.store import TransportStore, hash_prefix
29
from bzrlib.atomicfile import AtomicFile
30
from bzrlib.errors import NoSuchFile, FileExists
31
from bzrlib.symbol_versioning import *
32
from bzrlib.trace import mutter
35
class VersionedFileStore(TransportStore):
36
"""Collection of many versioned files in a transport."""
38
def __init__(self, transport, prefixed=False, precious=False,
39
dir_mode=None, file_mode=None,
40
versionedfile_class=WeaveFile):
41
super(WeaveStore, self).__init__(transport,
42
dir_mode=dir_mode, file_mode=file_mode,
43
prefixed=prefixed, compressed=False)
44
self._precious = precious
45
self._versionedfile_class = versionedfile_class
47
def _clear_cache_id(self, file_id, transaction):
48
"""WARNING may lead to inconsistent object references for file_id.
50
Remove file_id from the transaction map.
52
NOT in the transaction api because theres no reliable way to clear
53
callers. So its here for very specialised use rather than having an
56
weave = transaction.map.find_weave(file_id)
58
mutter("old data in transaction in %s for %s", self, file_id)
59
# FIXME abstraction violation - transaction now has stale data.
60
transaction.map.remove_object(weave)
62
def filename(self, file_id):
63
"""Return the path relative to the transport root."""
65
return hash_prefix(file_id) + urllib.quote(file_id)
67
return urllib.quote(file_id)
70
suffixes = self._versionedfile_class.get_suffixes()
72
for relpath in self._iter_files_recursive():
73
for suffix in suffixes:
74
if relpath.endswith(suffix):
75
id = os.path.basename(relpath[:-len(suffix)])
80
def has_id(self, fileid):
81
suffixes = self._versionedfile_class.get_suffixes()
82
filename = self.filename(fileid)
83
for suffix in suffixes:
84
if not self._transport.has(filename + suffix):
88
def get_empty(self, file_id, transaction):
89
"""Get an empty weave, which implies deleting the existing one first."""
90
if self.has_id(file_id):
91
self.delete(file_id, transaction)
92
return self.get_weave_or_empty(file_id, transaction)
94
def delete(self, file_id, transaction):
95
"""Remove file_id from the store."""
96
suffixes = self._versionedfile_class.get_suffixes()
97
filename = self.filename(file_id)
98
for suffix in suffixes:
99
self._transport.delete(filename + suffix)
100
self._clear_cache_id(file_id, transaction)
102
def get_weave(self, file_id, transaction):
103
weave = transaction.map.find_weave(file_id)
104
if weave is not None:
105
#mutter("cache hit in %s for %s", self, file_id)
107
if transaction.writeable():
108
w = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode)
109
transaction.map.add_weave(file_id, w)
110
transaction.register_dirty(w)
112
w = self._versionedfile_class(self.filename(file_id),
117
transaction.map.add_weave(file_id, w)
118
transaction.register_clean(w, precious=self._precious)
121
@deprecated_method(zero_eight)
122
def get_lines(self, file_id, rev_id, transaction):
123
"""Return text from a particular version of a weave.
125
Returned as a list of lines.
128
w = self.get_weave(file_id, transaction)
129
return w.get_lines(rev_id)
23
import bzrlib.weavefile
26
class WeaveStore(object):
27
"""Collection of several weave files."""
28
def __init__(self, dir):
32
def get_weave(self, file_id):
33
path = self._dir + os.sep + file_id + '.weave'
34
return bzrlib.weavefile.read_weave(file(path, 'rb'))
131
def _new_weave(self, file_id, transaction):
132
"""Make a new weave for file_id and return it."""
133
weave = self._make_new_versionedfile(file_id, transaction)
134
transaction.map.add_weave(file_id, weave)
135
# has to be dirty - its able to mutate on its own.
136
transaction.register_dirty(weave)
139
def _make_new_versionedfile(self, file_id, transaction):
140
if self.has_id(file_id):
141
self.delete(file_id, transaction)
143
weave = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode, create=True)
145
if not self._prefixed:
146
# unexpected error - NoSuchFile is raised on a missing dir only and that
147
# only occurs when we are prefixed.
149
self._transport.mkdir(hash_prefix(file_id), mode=self._dir_mode)
150
weave = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode, create=True)
153
def get_weave_or_empty(self, file_id, transaction):
154
"""Return a weave, or an empty one if it doesn't exist."""
156
return self.get_weave(file_id, transaction)
158
return self._new_weave(file_id, transaction)
160
@deprecated_method(zero_eight)
161
def put_weave(self, file_id, weave, transaction):
162
"""This is a deprecated API: It writes an entire collection of ids out.
164
This became inappropriate when we made a versioned file api which
165
tracks the state of the collection of versions for a single id.
167
Its maintained for backwards compatability but will only work on
168
weave stores - pre 0.8 repositories.
170
self._put_weave(self, file_id, weave, transaction)
172
def _put_weave(self, file_id, weave, transaction):
173
"""Preserved here for upgrades-to-weaves to use."""
174
myweave = self._make_new_versionedfile(file_id, transaction)
177
@deprecated_method(zero_eight)
178
def add_text(self, file_id, rev_id, new_lines, parents, transaction):
179
"""This method was a shorthand for
181
vfile = self.get_weave_or_empty(file_id, transaction)
182
vfile.add_lines(rev_id, parents, new_lines)
184
vfile = self.get_weave_or_empty(file_id, transaction)
185
vfile.add_lines(rev_id, parents, new_lines)
187
@deprecated_method(zero_eight)
188
def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
190
"""This method was a shorthand for
192
vfile = self.get_weave_or_empty(file_id, transaction)
193
vfile.clone_text(new_rev_id, old_rev_id, parents)
195
vfile = self.get_weave_or_empty(file_id, transaction)
196
vfile.clone_text(new_rev_id, old_rev_id, parents)
198
def copy(self, source, result_id, transaction):
199
"""Copy the source versioned file to result_id in this store."""
200
self._clear_cache_id(result_id, transaction)
201
source.copy_to(self.filename(result_id), self._transport)
203
def copy_all_ids(self, store_from, pb=None, from_transaction=None,
204
to_transaction=None):
205
"""Copy all the file ids from store_from into self."""
206
if from_transaction is None:
207
warn("Please pass from_transaction into "
208
"versioned_store.copy_all_ids.", stacklevel=2)
209
if to_transaction is None:
210
warn("Please pass to_transaction into "
211
"versioned_store.copy_all_ids.", stacklevel=2)
212
if not store_from.listable():
213
raise UnlistableStore(store_from)
215
for count, file_id in enumerate(store_from):
217
pb.update('listing files', count, count)
221
mutter('copy_all ids: %r', ids)
222
self.copy_multi(store_from, ids, pb=pb,
223
from_transaction=from_transaction,
224
to_transaction=to_transaction)
226
def copy_multi(self, from_store, file_ids, pb=None, from_transaction=None,
227
to_transaction=None):
228
"""Copy all the versions for multiple file_ids from from_store.
230
:param from_transaction: required current transaction in from_store.
232
from bzrlib.transactions import PassThroughTransaction
233
assert isinstance(from_store, WeaveStore)
234
if from_transaction is None:
235
warn("WeaveStore.copy_multi without a from_transaction parameter "
236
"is deprecated. Please provide a from_transaction.",
239
# we are reading one object - caching is irrelevant.
240
from_transaction = PassThroughTransaction()
241
if to_transaction is None:
242
warn("WeaveStore.copy_multi without a to_transaction parameter "
243
"is deprecated. Please provide a to_transaction.",
246
# we are copying single objects, and there may be open tranasactions
247
# so again with the passthrough
248
to_transaction = PassThroughTransaction()
249
for count, f in enumerate(file_ids):
250
mutter("copy weave {%s} into %s", f, self)
252
pb.update('copy', count, len(file_ids))
253
# if we have it in cache, its faster.
254
# joining is fast with knits, and bearable for weaves -
255
# indeed the new case can be optimised if needed.
256
target = self._make_new_versionedfile(f, to_transaction)
257
target.join(from_store.get_weave(f, from_transaction))
261
def total_size(self):
262
count, bytes = super(VersionedFileStore, self).total_size()
263
return (count / len(self._versionedfile_class.get_suffixes())), bytes
265
WeaveStore = VersionedFileStore