1
# Copyright (C) 2005, 2006 Canonical Ltd
3
# Copyright (C) 2005 Canonical Ltd
3
5
# This program is free software; you can redistribute it and/or modify
4
6
# it under the terms of the GNU General Public License as published by
5
7
# the Free Software Foundation; either version 2 of the License, or
6
8
# (at your option) any later version.
8
10
# This program is distributed in the hope that it will be useful,
9
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
13
# GNU General Public License for more details.
13
15
# You should have received a copy of the GNU General Public License
14
16
# along with this program; if not, write to the Free Software
15
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# XXX: Some consideration of the problems that might occur if there are
18
# files whose id differs only in case. That should probably be forbidden.
19
# Author: Martin Pool <mbp@canonical.com>
23
from cStringIO import StringIO
25
from warnings import warn
31
25
from bzrlib.weavefile import read_weave, write_weave_v5
32
from bzrlib.weave import WeaveFile, Weave
33
from bzrlib.store import TransportStore
26
from bzrlib.weave import Weave
34
27
from bzrlib.atomicfile import AtomicFile
35
from bzrlib.symbol_versioning import (deprecated_method,
37
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)
30
class WeaveStore(object):
31
"""Collection of several weave files."""
32
def __init__(self, dir):
73
36
def filename(self, file_id):
74
"""Return the path relative to the transport root."""
75
file_id = osutils.safe_file_id(file_id)
76
return self._relpath(file_id)
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)
117
def _get(self, file_id):
118
return self._transport.get(self.filename(file_id))
120
def _put(self, file_id, f):
121
fn = self.filename(file_id)
37
return self._dir + os.sep + file_id + '.weave'
40
def get_weave(self, file_id):
41
return read_weave(file(self.filename(file_id), 'rb'))
44
def get_weave_or_empty(self, file_id):
45
"""Return a weave, or an empty one if it doesn't exist."""
123
return self._transport.put_file(fn, f, mode=self._file_mode)
124
except errors.NoSuchFile:
125
if not self._prefixed:
47
inf = file(self.filename(file_id), 'rb')
49
if e.errno == errno.ENOENT:
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)
160
def _make_new_versionedfile(self, file_id, transaction,
161
known_missing=False, _filename=None):
162
"""Make a new versioned file.
164
:param _filename: filename that would be returned from self.filename for
165
file_id. This is used to reduce duplicate filename calculations when
166
using 'get_weave_or_empty'. FOR INTERNAL USE ONLY.
168
if not known_missing and self.has_id(file_id):
169
self.delete(file_id, transaction)
170
if _filename is None:
171
_filename = self.filename(file_id)
173
# we try without making the directory first because thats optimising
174
# for the common case.
175
weave = self._versionedfile_class(_filename, self._transport, self._file_mode, create=True,
176
**self._versionedfile_kwargs)
177
except errors.NoSuchFile:
178
if not self._prefixed:
179
# unexpected error - NoSuchFile is expected to be raised on a
180
# missing dir only and that only occurs when we are prefixed.
182
self._transport.mkdir(self.hash_prefix(file_id), mode=self._dir_mode)
183
weave = self._versionedfile_class(_filename, self._transport,
184
self._file_mode, create=True,
185
**self._versionedfile_kwargs)
188
def get_weave_or_empty(self, file_id, transaction):
189
"""Return a weave, or an empty one if it doesn't exist."""
190
# This is typically used from 'commit' and 'fetch/push/pull' where
191
# we scan across many versioned files once. As such the small overhead
192
# of calculating the filename before doing a cache lookup is more than
193
# compensated for by not calculating the filename when making new
195
file_id = osutils.safe_file_id(file_id)
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
def _put_weave(self, file_id, weave, transaction):
208
"""Preserved here for upgrades-to-weaves to use."""
209
myweave = self._make_new_versionedfile(file_id, transaction)
212
def copy(self, source, result_id, transaction):
213
"""Copy the source versioned file to result_id in this store."""
214
self._clear_cache_id(result_id, transaction)
215
source.copy_to(self.filename(result_id), self._transport)
217
def copy_all_ids(self, store_from, pb=None, from_transaction=None,
218
to_transaction=None):
219
"""Copy all the file ids from store_from into self."""
220
if from_transaction is None:
221
warn("Please pass from_transaction into "
222
"versioned_store.copy_all_ids.", stacklevel=2)
223
if to_transaction is None:
224
warn("Please pass to_transaction into "
225
"versioned_store.copy_all_ids.", stacklevel=2)
226
if not store_from.listable():
227
raise errors.UnlistableStore(store_from)
229
for count, file_id in enumerate(store_from):
231
pb.update('listing files', count, count)
235
mutter('copy_all ids: %r', ids)
236
self.copy_multi(store_from, ids, pb=pb,
237
from_transaction=from_transaction,
238
to_transaction=to_transaction)
240
def copy_multi(self, from_store, file_ids, pb=None, from_transaction=None,
241
to_transaction=None):
242
"""Copy all the versions for multiple file_ids from from_store.
244
:param from_transaction: required current transaction in from_store.
246
from bzrlib.transactions import PassThroughTransaction
247
assert isinstance(from_store, WeaveStore)
248
if from_transaction is None:
249
warn("WeaveStore.copy_multi without a from_transaction parameter "
250
"is deprecated. Please provide a from_transaction.",
253
# we are reading one object - caching is irrelevant.
254
from_transaction = PassThroughTransaction()
255
if to_transaction is None:
256
warn("WeaveStore.copy_multi without a to_transaction parameter "
257
"is deprecated. Please provide a to_transaction.",
260
# we are copying single objects, and there may be open tranasactions
261
# so again with the passthrough
262
to_transaction = PassThroughTransaction()
263
pb = bzrlib.ui.ui_factory.nested_progress_bar()
265
file_ids = [osutils.safe_file_id(f) for f in file_ids]
266
for count, f in enumerate(file_ids):
267
mutter("copy weave {%s} into %s", f, self)
268
pb.update('copy', count, len(file_ids))
269
# if we have it in cache, its faster.
270
# joining is fast with knits, and bearable for weaves -
271
# indeed the new case can be optimised if needed.
272
target = self._make_new_versionedfile(f, to_transaction)
273
target.join(from_store.get_weave(f, from_transaction))
54
return read_weave(inf)
57
def put_weave(self, file_id, weave):
58
"""Write back a modified weave"""
59
weave_fn = self.filename(file_id)
60
af = AtomicFile(weave_fn)
62
write_weave_v5(weave, af)
277
def total_size(self):
278
count, bytes = super(VersionedFileStore, self).total_size()
279
return (count / len(self._versionedfile_class.get_suffixes())), bytes
281
WeaveStore = VersionedFileStore
68
def add_text(self, file_id, rev_id, new_lines, parents):
69
w = self.get_weave_or_empty(file_id)
70
parent_idxs = map(w.lookup, parents)
71
w.add(rev_id, parent_idxs, new_lines)
72
self.put_weave(file_id, w)