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