18
20
# files whose id differs only in case. That should probably be forbidden.
23
from cStringIO import StringIO
23
from cStringIO import StringIO
26
27
from bzrlib.weavefile import read_weave, write_weave_v5
27
from bzrlib.weave import WeaveFile, Weave
28
from bzrlib.store import TransportStore
28
from bzrlib.weave import Weave
29
from bzrlib.store import TransportStore, hash_prefix
29
30
from bzrlib.atomicfile import AtomicFile
30
31
from bzrlib.errors import NoSuchFile, FileExists
31
from bzrlib.symbol_versioning import *
32
32
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)
35
class WeaveStore(TransportStore):
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, prefixed=False, precious=False):
43
self._transport = transport
44
self._prefixed = prefixed
49
45
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
47
def filename(self, file_id):
69
48
"""Return the path relative to the transport root."""
70
return self._relpath(file_id)
50
return hash_prefix(file_id) + file_id + WeaveStore.FILE_SUFFIX
52
return file_id + WeaveStore.FILE_SUFFIX
72
54
def __iter__(self):
73
suffixes = self._versionedfile_class.get_suffixes()
75
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
86
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)
55
l = len(WeaveStore.FILE_SUFFIX)
56
for relpath, st in self._iter_relpaths():
57
if relpath.endswith(WeaveStore.FILE_SUFFIX):
58
yield os.path.basename(relpath[:-l])
60
def __contains__(self, fileid):
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)
70
self._transport.mkdir(hash_prefix(file_id))
73
return self._transport.put(self.filename(file_id), f)
121
75
def get_weave(self, file_id, transaction):
122
76
weave = transaction.map.find_weave(file_id)
123
if weave is not None:
124
#mutter("cache hit in %s for %s", self, file_id)
78
mutter("cache hit in %s for %s", self, file_id)
126
if transaction.writeable():
127
w = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode,
128
**self._versionedfile_kwargs)
129
transaction.map.add_weave(file_id, w)
130
transaction.register_dirty(w)
132
w = self._versionedfile_class(self.filename(file_id),
137
**self._versionedfile_kwargs)
138
transaction.map.add_weave(file_id, w)
139
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)
142
@deprecated_method(zero_eight)
143
85
def get_lines(self, file_id, rev_id, transaction):
144
86
"""Return text from a particular version of a weave.
146
Returned as a list of lines.
88
Returned as a list of lines."""
148
89
w = self.get_weave(file_id, transaction)
149
return w.get_lines(rev_id)
90
return w.get(w.lookup(rev_id))
151
def _new_weave(self, file_id, transaction):
152
"""Make a new weave for file_id and return it."""
153
weave = self._make_new_versionedfile(file_id, transaction)
154
transaction.map.add_weave(file_id, weave)
155
# has to be dirty - its able to mutate on its own.
156
transaction.register_dirty(weave)
159
def _make_new_versionedfile(self, file_id, transaction):
160
if self.has_id(file_id):
161
self.delete(file_id, transaction)
163
weave = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode, create=True,
164
**self._versionedfile_kwargs)
166
if not self._prefixed:
167
# unexpected error - NoSuchFile is raised on a missing dir only and that
168
# only occurs when we are prefixed.
170
self._transport.mkdir(self.hash_prefix(file_id), mode=self._dir_mode)
171
weave = self._versionedfile_class(self.filename(file_id), self._transport,
172
self._file_mode, create=True,
173
**self._versionedfile_kwargs)
176
92
def get_weave_or_empty(self, file_id, transaction):
177
93
"""Return a weave, or an empty one if it doesn't exist."""
179
95
return self.get_weave(file_id, transaction)
180
96
except NoSuchFile:
181
return self._new_weave(file_id, transaction)
97
weave = Weave(weave_name=file_id)
98
transaction.map.add_weave(file_id, weave)
99
transaction.register_clean(weave, precious=self._precious)
183
@deprecated_method(zero_eight)
184
102
def put_weave(self, file_id, weave, transaction):
185
"""This is a deprecated API: It writes an entire collection of ids out.
187
This became inappropriate when we made a versioned file api which
188
tracks the state of the collection of versions for a single id.
190
Its maintained for backwards compatability but will only work on
191
weave stores - pre 0.8 repositories.
193
self._put_weave(file_id, weave, transaction)
195
def _put_weave(self, file_id, weave, transaction):
196
"""Preserved here for upgrades-to-weaves to use."""
197
myweave = self._make_new_versionedfile(file_id, transaction)
200
@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)
201
112
def add_text(self, file_id, rev_id, new_lines, parents, transaction):
202
"""This method was a shorthand for
204
vfile = self.get_weave_or_empty(file_id, transaction)
205
vfile.add_lines(rev_id, parents, new_lines)
207
vfile = self.get_weave_or_empty(file_id, transaction)
208
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)
210
@deprecated_method(zero_eight)
211
118
def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
213
"""This method was a shorthand for
215
vfile = self.get_weave_or_empty(file_id, transaction)
216
vfile.clone_text(new_rev_id, old_rev_id, parents)
218
vfile = self.get_weave_or_empty(file_id, transaction)
219
vfile.clone_text(new_rev_id, old_rev_id, parents)
221
def copy(self, source, result_id, transaction):
222
"""Copy the source versioned file to result_id in this store."""
223
self._clear_cache_id(result_id, transaction)
224
source.copy_to(self.filename(result_id), self._transport)
226
def copy_all_ids(self, store_from, pb=None, from_transaction=None,
227
to_transaction=None):
228
"""Copy all the file ids from store_from into self."""
229
if from_transaction is None:
230
warn("Please pass from_transaction into "
231
"versioned_store.copy_all_ids.", stacklevel=2)
232
if to_transaction is None:
233
warn("Please pass to_transaction into "
234
"versioned_store.copy_all_ids.", stacklevel=2)
235
if not store_from.listable():
236
raise UnlistableStore(store_from)
238
for count, file_id in enumerate(store_from):
240
pb.update('listing files', count, count)
244
mutter('copy_all ids: %r', ids)
245
self.copy_multi(store_from, ids, pb=pb,
246
from_transaction=from_transaction,
247
to_transaction=to_transaction)
249
def copy_multi(self, from_store, file_ids, pb=None, from_transaction=None,
250
to_transaction=None):
251
"""Copy all the versions for multiple file_ids from from_store.
253
:param from_transaction: required current transaction in from_store.
255
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):
256
126
assert isinstance(from_store, WeaveStore)
257
if from_transaction is None:
258
warn("WeaveStore.copy_multi without a from_transaction parameter "
259
"is deprecated. Please provide a from_transaction.",
262
# we are reading one object - caching is irrelevant.
263
from_transaction = PassThroughTransaction()
264
if to_transaction is None:
265
warn("WeaveStore.copy_multi without a to_transaction parameter "
266
"is deprecated. Please provide a to_transaction.",
269
# we are copying single objects, and there may be open tranasactions
270
# so again with the passthrough
271
to_transaction = PassThroughTransaction()
272
pb = bzrlib.ui.ui_factory.nested_progress_bar()
273
for count, f in enumerate(file_ids):
274
128
mutter("copy weave {%s} into %s", f, self)
275
pb.update('copy', count, len(file_ids))
276
# if we have it in cache, its faster.
277
# joining is fast with knits, and bearable for weaves -
278
# indeed the new case can be optimised if needed.
279
target = self._make_new_versionedfile(f, to_transaction)
280
target.join(from_store.get_weave(f, from_transaction))
283
def total_size(self):
284
count, bytes = super(VersionedFileStore, self).total_size()
285
return (count / len(self._versionedfile_class.get_suffixes())), bytes
287
WeaveStore = VersionedFileStore
129
self._put(f, from_store._get(f))