26
28
from bzrlib.weavefile import read_weave, write_weave_v5
27
from bzrlib.weave import WeaveFile, Weave
28
from bzrlib.store import TransportStore
29
from bzrlib.weave import Weave
30
from bzrlib.store import TransportStore, hash_prefix
29
31
from bzrlib.atomicfile import AtomicFile
30
32
from bzrlib.errors import NoSuchFile, FileExists
31
from bzrlib.symbol_versioning import *
32
33
from bzrlib.trace import mutter
36
class VersionedFileStore(TransportStore):
37
"""Collection of many versioned files in a transport."""
39
# TODO: Rather than passing versionedfile_kwargs, perhaps pass in a
40
# transport factory callable?
41
def __init__(self, transport, prefixed=False, precious=False,
42
dir_mode=None, file_mode=None,
43
versionedfile_class=WeaveFile,
44
versionedfile_kwargs={},
46
super(VersionedFileStore, self).__init__(transport,
47
dir_mode=dir_mode, file_mode=file_mode,
48
prefixed=prefixed, compressed=False, escaped=escaped)
36
class WeaveStore(TransportStore):
37
"""Collection of several weave files in a directory.
39
This has some shortcuts for reading and writing them.
41
FILE_SUFFIX = '.weave'
43
def __init__(self, transport, prefixed=False, precious=False):
44
self._transport = transport
45
self._prefixed = prefixed
49
46
self._precious = precious
50
self._versionedfile_class = versionedfile_class
51
self._versionedfile_kwargs = versionedfile_kwargs
53
def _clear_cache_id(self, file_id, transaction):
54
"""WARNING may lead to inconsistent object references for file_id.
56
Remove file_id from the transaction map.
58
NOT in the transaction api because theres no reliable way to clear
59
callers. So its here for very specialised use rather than having an
62
weave = transaction.map.find_weave(file_id)
64
mutter("old data in transaction in %s for %s", self, file_id)
65
# FIXME abstraction violation - transaction now has stale data.
66
transaction.map.remove_object(weave)
68
48
def filename(self, file_id):
69
49
"""Return the path relative to the transport root."""
70
return self._relpath(file_id)
51
return hash_prefix(file_id) + urllib.quote(file_id) + WeaveStore.FILE_SUFFIX
53
return urllib.quote(file_id) + WeaveStore.FILE_SUFFIX
72
55
def __iter__(self):
73
suffixes = self._versionedfile_class.get_suffixes()
56
l = len(WeaveStore.FILE_SUFFIX)
75
57
for relpath in self._iter_files_recursive():
76
for suffix in suffixes:
77
if relpath.endswith(suffix):
78
# TODO: use standard remove_suffix function
79
escaped_id = os.path.basename(relpath[:-len(suffix)])
80
file_id = self._unescape(escaped_id)
81
if file_id not in ids:
84
break # only one suffix can match
58
if relpath.endswith(WeaveStore.FILE_SUFFIX):
59
yield os.path.basename(relpath[:-l])
86
61
def has_id(self, fileid):
87
suffixes = self._versionedfile_class.get_suffixes()
88
filename = self.filename(fileid)
89
for suffix in suffixes:
90
if not self._transport.has(filename + suffix):
94
def get_empty(self, file_id, transaction):
95
"""Get an empty weave, which implies deleting the existing one first."""
96
if self.has_id(file_id):
97
self.delete(file_id, transaction)
98
return self.get_weave_or_empty(file_id, transaction)
100
def delete(self, file_id, transaction):
101
"""Remove file_id from the store."""
102
suffixes = self._versionedfile_class.get_suffixes()
103
filename = self.filename(file_id)
104
for suffix in suffixes:
105
self._transport.delete(filename + suffix)
106
self._clear_cache_id(file_id, transaction)
62
return self._transport.has(self.filename(fileid))
108
64
def _get(self, file_id):
109
65
return self._transport.get(self.filename(file_id))
111
67
def _put(self, file_id, f):
112
fn = self.filename(file_id)
114
return self._transport.put(fn, f, mode=self._file_mode)
116
if not self._prefixed:
118
self._transport.mkdir(os.path.dirname(fn), mode=self._dir_mode)
119
return self._transport.put(fn, f, mode=self._file_mode)
121
def get_weave(self, file_id, transaction, _filename=None):
122
"""Return the VersionedFile for file_id.
124
:param _filename: filename that would be returned from self.filename for
125
file_id. This is used to reduce duplicate filename calculations when
126
using 'get_weave_or_empty'. FOR INTERNAL USE ONLY.
70
self._transport.mkdir(hash_prefix(file_id))
73
return self._transport.put(self.filename(file_id), f)
75
def get_weave(self, file_id, transaction):
128
76
weave = transaction.map.find_weave(file_id)
129
if weave is not None:
130
#mutter("cache hit in %s for %s", self, file_id)
78
mutter("cache hit in %s for %s", self, file_id)
132
if _filename is None:
133
_filename = self.filename(file_id)
134
if transaction.writeable():
135
w = self._versionedfile_class(_filename, self._transport, self._file_mode,
136
**self._versionedfile_kwargs)
137
transaction.map.add_weave(file_id, w)
138
transaction.register_dirty(w)
140
w = self._versionedfile_class(_filename,
145
**self._versionedfile_kwargs)
146
transaction.map.add_weave(file_id, w)
147
transaction.register_clean(w, precious=self._precious)
80
w = read_weave(self._get(file_id))
81
transaction.map.add_weave(file_id, w)
82
transaction.register_clean(w, precious=self._precious)
150
@deprecated_method(zero_eight)
151
85
def get_lines(self, file_id, rev_id, transaction):
152
86
"""Return text from a particular version of a weave.
154
Returned as a list of lines.
88
Returned as a list of lines."""
156
89
w = self.get_weave(file_id, transaction)
157
return w.get_lines(rev_id)
90
return w.get(w.lookup(rev_id))
159
def _make_new_versionedfile(self, file_id, transaction,
160
known_missing=False, _filename=None):
161
"""Make a new versioned file.
163
:param _filename: filename that would be returned from self.filename for
164
file_id. This is used to reduce duplicate filename calculations when
165
using 'get_weave_or_empty'. FOR INTERNAL USE ONLY.
167
if not known_missing and self.has_id(file_id):
168
self.delete(file_id, transaction)
169
if _filename is None:
170
_filename = self.filename(file_id)
172
# we try without making the directory first because thats optimising
173
# for the common case.
174
weave = self._versionedfile_class(_filename, self._transport, self._file_mode, create=True,
175
**self._versionedfile_kwargs)
177
if not self._prefixed:
178
# unexpected error - NoSuchFile is expected to be raised on a
179
# missing dir only and that only occurs when we are prefixed.
181
self._transport.mkdir(self.hash_prefix(file_id), mode=self._dir_mode)
182
weave = self._versionedfile_class(_filename, self._transport,
183
self._file_mode, create=True,
184
**self._versionedfile_kwargs)
187
92
def get_weave_or_empty(self, file_id, transaction):
188
"""Return a weave, or an empty one if it doesn't exist."""
189
# This is typically used from 'commit' and 'fetch/push/pull' where
190
# we scan across many versioned files once. As such the small overhead
191
# of calculating the filename before doing a cache lookup is more than
192
# compensated for by not calculating the filename when making new
194
_filename = self.filename(file_id)
93
"""Return a weave, or an empty one if it doesn't exist."""
196
return self.get_weave(file_id, transaction, _filename=_filename)
95
return self.get_weave(file_id, transaction)
197
96
except NoSuchFile:
198
weave = self._make_new_versionedfile(file_id, transaction,
199
known_missing=True, _filename=_filename)
97
weave = Weave(weave_name=file_id)
200
98
transaction.map.add_weave(file_id, weave)
201
# has to be dirty - its able to mutate on its own.
202
transaction.register_dirty(weave)
99
transaction.register_clean(weave, precious=self._precious)
205
@deprecated_method(zero_eight)
206
102
def put_weave(self, file_id, weave, transaction):
207
"""This is a deprecated API: It writes an entire collection of ids out.
209
This became inappropriate when we made a versioned file api which
210
tracks the state of the collection of versions for a single id.
212
Its maintained for backwards compatability but will only work on
213
weave stores - pre 0.8 repositories.
215
self._put_weave(file_id, weave, transaction)
217
def _put_weave(self, file_id, weave, transaction):
218
"""Preserved here for upgrades-to-weaves to use."""
219
myweave = self._make_new_versionedfile(file_id, transaction)
222
@deprecated_method(zero_eight)
103
"""Write back a modified weave"""
104
transaction.register_dirty(weave)
105
# TODO FOR WRITE TRANSACTIONS: this should be done in a callback
106
# from the transaction, when it decides to save.
108
write_weave_v5(weave, sio)
110
self._put(file_id, sio)
223
112
def add_text(self, file_id, rev_id, new_lines, parents, transaction):
224
"""This method was a shorthand for
226
vfile = self.get_weave_or_empty(file_id, transaction)
227
vfile.add_lines(rev_id, parents, new_lines)
229
vfile = self.get_weave_or_empty(file_id, transaction)
230
vfile.add_lines(rev_id, parents, new_lines)
113
w = self.get_weave_or_empty(file_id, transaction)
114
parent_idxs = map(w.lookup, parents)
115
w.add(rev_id, parent_idxs, new_lines)
116
self.put_weave(file_id, w, transaction)
232
@deprecated_method(zero_eight)
233
118
def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
235
"""This method was a shorthand for
237
vfile = self.get_weave_or_empty(file_id, transaction)
238
vfile.clone_text(new_rev_id, old_rev_id, parents)
240
vfile = self.get_weave_or_empty(file_id, transaction)
241
vfile.clone_text(new_rev_id, old_rev_id, parents)
243
def copy(self, source, result_id, transaction):
244
"""Copy the source versioned file to result_id in this store."""
245
self._clear_cache_id(result_id, transaction)
246
source.copy_to(self.filename(result_id), self._transport)
248
def copy_all_ids(self, store_from, pb=None, from_transaction=None,
249
to_transaction=None):
250
"""Copy all the file ids from store_from into self."""
251
if from_transaction is None:
252
warn("Please pass from_transaction into "
253
"versioned_store.copy_all_ids.", stacklevel=2)
254
if to_transaction is None:
255
warn("Please pass to_transaction into "
256
"versioned_store.copy_all_ids.", stacklevel=2)
257
if not store_from.listable():
258
raise UnlistableStore(store_from)
260
for count, file_id in enumerate(store_from):
262
pb.update('listing files', count, count)
266
mutter('copy_all ids: %r', ids)
267
self.copy_multi(store_from, ids, pb=pb,
268
from_transaction=from_transaction,
269
to_transaction=to_transaction)
271
def copy_multi(self, from_store, file_ids, pb=None, from_transaction=None,
272
to_transaction=None):
273
"""Copy all the versions for multiple file_ids from from_store.
275
:param from_transaction: required current transaction in from_store.
277
from bzrlib.transactions import PassThroughTransaction
120
w = self.get_weave_or_empty(file_id, transaction)
121
parent_idxs = map(w.lookup, parents)
122
w.add_identical(old_rev_id, new_rev_id, parent_idxs)
123
self.put_weave(file_id, w, transaction)
125
def copy_multi(self, from_store, file_ids):
278
126
assert isinstance(from_store, WeaveStore)
279
if from_transaction is None:
280
warn("WeaveStore.copy_multi without a from_transaction parameter "
281
"is deprecated. Please provide a from_transaction.",
284
# we are reading one object - caching is irrelevant.
285
from_transaction = PassThroughTransaction()
286
if to_transaction is None:
287
warn("WeaveStore.copy_multi without a to_transaction parameter "
288
"is deprecated. Please provide a to_transaction.",
291
# we are copying single objects, and there may be open tranasactions
292
# so again with the passthrough
293
to_transaction = PassThroughTransaction()
294
pb = bzrlib.ui.ui_factory.nested_progress_bar()
295
for count, f in enumerate(file_ids):
296
128
mutter("copy weave {%s} into %s", f, self)
297
pb.update('copy', count, len(file_ids))
298
# if we have it in cache, its faster.
299
# joining is fast with knits, and bearable for weaves -
300
# indeed the new case can be optimised if needed.
301
target = self._make_new_versionedfile(f, to_transaction)
302
target.join(from_store.get_weave(f, from_transaction))
305
def total_size(self):
306
count, bytes = super(VersionedFileStore, self).total_size()
307
return (count / len(self._versionedfile_class.get_suffixes())), bytes
309
WeaveStore = VersionedFileStore
129
self._put(f, from_store._get(f))