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