26
26
from bzrlib.weavefile import read_weave, write_weave_v5
27
from bzrlib.weave import Weave
27
from bzrlib.weave import WeaveFile
28
28
from bzrlib.store import TransportStore, hash_prefix
29
29
from bzrlib.atomicfile import AtomicFile
30
30
from bzrlib.errors import NoSuchFile, FileExists
31
from bzrlib.symbol_versioning import *
31
32
from bzrlib.trace import mutter
34
class WeaveStore(TransportStore):
35
"""Collection of several weave files in a directory.
37
This has some shortcuts for reading and writing them.
39
FILE_SUFFIX = '.weave'
35
class VersionedFileStore(TransportStore):
36
"""Collection of many versioned files in a transport."""
41
38
def __init__(self, transport, prefixed=False, precious=False,
42
dir_mode=None, file_mode=None):
39
dir_mode=None, file_mode=None,
40
versionedfile_class=WeaveFile):
43
41
super(WeaveStore, self).__init__(transport,
44
42
dir_mode=dir_mode, file_mode=file_mode,
45
43
prefixed=prefixed, compressed=False)
46
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)
48
62
def filename(self, file_id):
49
63
"""Return the path relative to the transport root."""
51
return hash_prefix(file_id) + urllib.quote(file_id) + WeaveStore.FILE_SUFFIX
65
return hash_prefix(file_id) + urllib.quote(file_id)
53
return urllib.quote(file_id) + WeaveStore.FILE_SUFFIX
67
return urllib.quote(file_id)
55
69
def __iter__(self):
56
l = len(WeaveStore.FILE_SUFFIX)
70
suffixes = self._versionedfile_class.get_suffixes()
57
72
for relpath in self._iter_files_recursive():
58
if relpath.endswith(WeaveStore.FILE_SUFFIX):
59
yield os.path.basename(relpath[:-l])
73
for suffix in suffixes:
74
if relpath.endswith(suffix):
75
id = os.path.basename(relpath[:-len(suffix)])
61
80
def has_id(self, fileid):
62
return self._transport.has(self.filename(fileid))
64
def _get(self, file_id):
65
return self._transport.get(self.filename(file_id))
67
def _put(self, file_id, f):
68
# less round trips to mkdir on failure than mkdir always
70
return self._transport.put(self.filename(file_id), f, mode=self._file_mode)
72
if not self._prefixed:
74
self._transport.mkdir(hash_prefix(file_id), mode=self._dir_mode)
75
return self._transport.put(self.filename(file_id), f, mode=self._file_mode)
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)
77
102
def get_weave(self, file_id, transaction):
78
103
weave = transaction.map.find_weave(file_id)
104
if weave is not None:
80
105
mutter("cache hit in %s for %s", self, file_id)
82
w = read_weave(self._get(file_id))
107
w = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode)
83
108
transaction.map.add_weave(file_id, w)
84
109
transaction.register_clean(w, precious=self._precious)
85
# TODO: jam 20051219 This should check if there is a prelude
86
# which is already cached, and if so, should remove it
87
# But transaction doesn't seem to have a 'remove'
88
# One workaround would be to re-add the object with
92
def get_weave_prelude(self, file_id, transaction):
94
weave = transaction.map.find_weave(weave_id)
96
mutter("cache hit in %s for %s", self, weave_id)
98
# We want transactions to also cache preludes if that
99
# is all that we are loading. So we need a unique
100
# identifier, so that someone who wants the whole text
101
# won't get just the prelude
102
weave_id = 'PRELUDE-' + file_id
103
weave = transaction.map.find_weave(weave_id)
105
mutter("cache hit in %s for %s", self, weave_id)
107
w = read_weave(self._get(file_id), prelude=True)
108
transaction.map.add_weave(weave_id, w)
109
transaction.register_clean(w, precious=self._precious)
112
@deprecated_method(zero_eight)
112
113
def get_lines(self, file_id, rev_id, transaction):
113
114
"""Return text from a particular version of a weave.
115
Returned as a list of lines."""
116
Returned as a list of lines.
116
119
w = self.get_weave(file_id, transaction)
117
return w.get(w.lookup(rev_id))
120
return w.get_lines(rev_id)
119
def get_weave_prelude_or_empty(self, file_id, transaction):
120
"""cheap version that reads the prelude but not the lines
122
def _new_weave(self, file_id, transaction):
123
"""Make a new weave for file_id and return it."""
124
weave = self._make_new_versionedfile(file_id, transaction)
125
transaction.map.add_weave(file_id, weave)
126
transaction.register_clean(weave, precious=self._precious)
129
def _make_new_versionedfile(self, file_id, transaction):
130
if self.has_id(file_id):
131
self.delete(file_id, transaction)
123
return self.get_weave_prelude(file_id, transaction)
133
weave = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode, create=True)
124
134
except NoSuchFile:
125
# We can cache here, because we know that there
126
# is no complete object, since we got NoSuchFile
127
weave = Weave(weave_name=file_id)
128
transaction.map.add_weave(file_id, weave)
129
transaction.register_clean(weave, precious=self._precious)
135
if not self._prefixed:
136
# unexpected error - NoSuchFile is raised on a missing dir only and that
137
# only occurs when we are prefixed.
139
self._transport.mkdir(hash_prefix(file_id), mode=self._dir_mode)
140
weave = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode, create=True)
132
143
def get_weave_or_empty(self, file_id, transaction):
133
144
"""Return a weave, or an empty one if it doesn't exist."""
135
146
return self.get_weave(file_id, transaction)
136
147
except NoSuchFile:
137
weave = Weave(weave_name=file_id)
138
transaction.map.add_weave(file_id, weave)
139
transaction.register_clean(weave, precious=self._precious)
148
return self._new_weave(file_id, transaction)
150
@deprecated_method(zero_eight)
142
151
def put_weave(self, file_id, weave, transaction):
143
"""Write back a modified weave"""
144
transaction.register_dirty(weave)
145
# TODO FOR WRITE TRANSACTIONS: this should be done in a callback
146
# from the transaction, when it decides to save.
148
write_weave_v5(weave, sio)
150
self._put(file_id, sio)
152
"""This is a deprecated API: It writes an entire collection of ids out.
154
This became inappropriate when we made a versioned file api which
155
tracks the state of the collection of versions for a single id.
157
Its maintained for backwards compatability but will only work on
158
weave stores - pre 0.8 repositories.
160
self._put_weave(self, file_id, weave, transaction)
162
def _put_weave(self, file_id, weave, transaction):
163
"""Preserved here for upgrades-to-weaves to use."""
164
myweave = self._make_new_versionedfile(file_id, transaction)
167
@deprecated_method(zero_eight)
152
168
def add_text(self, file_id, rev_id, new_lines, parents, transaction):
153
w = self.get_weave_or_empty(file_id, transaction)
154
parent_idxs = map(w.lookup, parents)
155
w.add(rev_id, parent_idxs, new_lines)
156
self.put_weave(file_id, w, transaction)
169
"""This method was a shorthand for
171
vfile = self.get_weave_or_empty(file_id, transaction)
172
vfile.add_lines(rev_id, parents, new_lines)
174
vfile = self.get_weave_or_empty(file_id, transaction)
175
vfile.add_lines(rev_id, parents, new_lines)
177
@deprecated_method(zero_eight)
158
178
def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
160
w = self.get_weave_or_empty(file_id, transaction)
161
parent_idxs = map(w.lookup, parents)
162
w.add_identical(old_rev_id, new_rev_id, parent_idxs)
163
self.put_weave(file_id, w, transaction)
165
def copy_multi(self, from_store, file_ids, pb=None):
180
"""This method was a shorthand for
182
vfile = self.get_weave_or_empty(file_id, transaction)
183
vfile.clone_text(new_rev_id, old_rev_id, parents)
185
vfile = self.get_weave_or_empty(file_id, transaction)
186
vfile.clone_text(new_rev_id, old_rev_id, parents)
188
def copy(self, source, result_id, transaction):
189
"""Copy the source versioned file to result_id in this store."""
190
self._clear_cache_id(result_id, transaction)
191
source.copy_to(self.filename(result_id), self._transport)
193
def copy_all_ids(self, store_from, pb=None, from_transaction=None,
194
to_transaction=None):
195
"""Copy all the file ids from store_from into self."""
196
if from_transaction is None:
197
warn("Please pass from_transaction into "
198
"versioned_store.copy_all_ids.", stacklevel=2)
199
if to_transaction is None:
200
warn("Please pass to_transaction into "
201
"versioned_store.copy_all_ids.", stacklevel=2)
202
if not store_from.listable():
203
raise UnlistableStore(store_from)
205
for count, file_id in enumerate(store_from):
207
pb.update('listing files', count, count)
211
mutter('copy_all ids: %r', ids)
212
self.copy_multi(store_from, ids, pb=pb,
213
from_transaction=from_transaction,
214
to_transaction=to_transaction)
216
def copy_multi(self, from_store, file_ids, pb=None, from_transaction=None,
217
to_transaction=None):
218
"""Copy all the versions for multiple file_ids from from_store.
220
:param from_transaction: required current transaction in from_store.
222
from bzrlib.transactions import PassThroughTransaction
166
223
assert isinstance(from_store, WeaveStore)
224
if from_transaction is None:
225
warn("WeaveStore.copy_multi without a from_transaction parameter "
226
"is deprecated. Please provide a from_transaction.",
229
# we are reading one object - caching is irrelevant.
230
from_transaction = PassThroughTransaction()
231
if to_transaction is None:
232
warn("WeaveStore.copy_multi without a to_transaction parameter "
233
"is deprecated. Please provide a to_transaction.",
236
# we are copying single objects, and there may be open tranasactions
237
# so again with the passthrough
238
to_transaction = PassThroughTransaction()
167
239
for count, f in enumerate(file_ids):
168
240
mutter("copy weave {%s} into %s", f, self)
170
242
pb.update('copy', count, len(file_ids))
171
self._put(f, from_store._get(f))
243
# if we have it in cache, its faster.
244
# joining is fast with knits, and bearable for weaves -
245
# indeed the new case can be optimised if needed.
246
target = self._make_new_versionedfile(f, to_transaction)
247
target.join(from_store.get_weave(f, from_transaction))
251
def total_size(self):
252
count, bytes = super(VersionedFileStore, self).total_size()
253
return (count / len(self._versionedfile_class.get_suffixes())), bytes
255
WeaveStore = VersionedFileStore