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
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
19
# XXX: Some consideration of the problems that might occur if there are
20
20
# files whose id differs only in case. That should probably be forbidden.
23
from cStringIO import StringIO
24
from warnings import warn
30
from bzrlib.store import TransportStore
27
from bzrlib.weavefile import read_weave, write_weave_v5
28
from bzrlib.weave import Weave
29
from bzrlib.store import Store
30
from bzrlib.atomicfile import AtomicFile
31
from bzrlib.errors import NoSuchFile
31
32
from bzrlib.trace import mutter
35
class VersionedFileStore(TransportStore):
36
"""Collection of many versioned files in a transport."""
38
# TODO: Rather than passing versionedfile_kwargs, perhaps pass in a
39
# transport factory callable?
40
def __init__(self, transport, prefixed=False, precious=False,
41
dir_mode=None, file_mode=None,
42
versionedfile_class=None,
43
versionedfile_kwargs={},
45
super(VersionedFileStore, self).__init__(transport,
46
dir_mode=dir_mode, file_mode=file_mode,
47
prefixed=prefixed, compressed=False, escaped=escaped)
48
self._precious = precious
49
self._versionedfile_class = versionedfile_class
50
self._versionedfile_kwargs = versionedfile_kwargs
51
# Used for passing get_scope to versioned file constructors;
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
54
48
def filename(self, file_id):
55
49
"""Return the path relative to the transport root."""
56
return self._relpath(file_id)
50
return file_id + WeaveStore.FILE_SUFFIX
58
52
def __iter__(self):
59
suffixes = self._versionedfile_class.get_suffixes()
61
for relpath in self._iter_files_recursive():
62
for suffix in suffixes:
63
if relpath.endswith(suffix):
64
# TODO: use standard remove_suffix function
65
escaped_id = os.path.basename(relpath[:-len(suffix)])
66
file_id = self._mapper.unmap(escaped_id)[0]
67
if file_id not in ids:
70
break # only one suffix can match
72
def has_id(self, file_id):
73
suffixes = self._versionedfile_class.get_suffixes()
74
filename = self.filename(file_id)
75
for suffix in suffixes:
76
if not self._transport.has(filename + suffix):
80
def get_empty(self, file_id, transaction):
81
"""Get an empty weave, which implies deleting the existing one first."""
82
if self.has_id(file_id):
83
self.delete(file_id, transaction)
84
return self.get_weave_or_empty(file_id, transaction)
86
def delete(self, file_id, transaction):
87
"""Remove file_id from the store."""
88
suffixes = self._versionedfile_class.get_suffixes()
89
filename = self.filename(file_id)
90
for suffix in suffixes:
91
self._transport.delete(filename + suffix)
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))
93
63
def _get(self, file_id):
94
64
return self._transport.get(self.filename(file_id))
96
66
def _put(self, file_id, f):
97
fn = self.filename(file_id)
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
80
def get_lines(self, file_id, rev_id):
81
"""Return text from a particular version of a weave.
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."""
99
return self._transport.put_file(fn, f, mode=self._file_mode)
100
except errors.NoSuchFile:
101
if not self._prefixed:
103
self._transport.mkdir(os.path.dirname(fn), mode=self._dir_mode)
104
return self._transport.put_file(fn, f, mode=self._file_mode)
106
def get_weave(self, file_id, transaction, _filename=None):
107
"""Return the VersionedFile for file_id.
109
:param _filename: filename that would be returned from self.filename for
110
file_id. This is used to reduce duplicate filename calculations when
111
using 'get_weave_or_empty'. FOR INTERNAL USE ONLY.
113
if _filename is None:
114
_filename = self.filename(file_id)
115
if transaction.writeable():
116
w = self._versionedfile_class(_filename, self._transport, self._file_mode,
117
get_scope=self.get_scope, **self._versionedfile_kwargs)
91
inf = self._get(file_id)
93
return Weave(weave_name=file_id)
119
w = self._versionedfile_class(_filename,
124
get_scope=self.get_scope,
125
**self._versionedfile_kwargs)
128
def _make_new_versionedfile(self, file_id, transaction,
129
known_missing=False, _filename=None):
130
"""Make a new versioned file.
132
:param _filename: filename that would be returned from self.filename for
133
file_id. This is used to reduce duplicate filename calculations when
134
using 'get_weave_or_empty'. FOR INTERNAL USE ONLY.
136
if not known_missing and self.has_id(file_id):
137
self.delete(file_id, transaction)
138
if _filename is None:
139
_filename = self.filename(file_id)
141
# we try without making the directory first because thats optimising
142
# for the common case.
143
weave = self._versionedfile_class(_filename, self._transport, self._file_mode, create=True,
144
get_scope=self.get_scope, **self._versionedfile_kwargs)
145
except errors.NoSuchFile:
146
if not self._prefixed:
147
# unexpected error - NoSuchFile is expected to be raised on a
148
# missing dir only and that only occurs when we are prefixed.
150
dirname = osutils.dirname(_filename)
151
self._transport.mkdir(dirname, mode=self._dir_mode)
152
weave = self._versionedfile_class(_filename, self._transport,
153
self._file_mode, create=True,
154
get_scope=self.get_scope,
155
**self._versionedfile_kwargs)
158
def get_weave_or_empty(self, file_id, transaction):
159
"""Return a weave, or an empty one if it doesn't exist."""
160
# This is typically used from 'commit' and 'fetch/push/pull' where
161
# we scan across many versioned files once. As such the small overhead
162
# of calculating the filename before doing a cache lookup is more than
163
# compensated for by not calculating the filename when making new
165
_filename = self.filename(file_id)
167
return self.get_weave(file_id, transaction, _filename=_filename)
168
except errors.NoSuchFile:
169
weave = self._make_new_versionedfile(file_id, transaction,
170
known_missing=True, _filename=_filename)
173
def _put_weave(self, file_id, weave, transaction):
174
"""Preserved here for upgrades-to-weaves to use."""
175
myweave = self._make_new_versionedfile(file_id, transaction)
176
myweave.insert_record_stream(weave.get_record_stream(
177
[(version,) for version in weave.versions()],
178
'topological', False))
180
def copy_all_ids(self, store_from, pb=None, from_transaction=None,
181
to_transaction=None):
182
"""Copy all the file ids from store_from into self."""
183
if from_transaction is None:
184
warn("Please pass from_transaction into "
185
"versioned_store.copy_all_ids.", stacklevel=2)
186
if to_transaction is None:
187
warn("Please pass to_transaction into "
188
"versioned_store.copy_all_ids.", stacklevel=2)
189
if not store_from.listable():
190
raise errors.UnlistableStore(store_from)
192
for count, file_id in enumerate(store_from):
194
pb.update('listing files', count, count)
198
mutter('copy_all ids: %r', ids)
199
self.copy_multi(store_from, ids, pb=pb,
200
from_transaction=from_transaction,
201
to_transaction=to_transaction)
203
def copy_multi(self, from_store, file_ids, pb=None, from_transaction=None,
204
to_transaction=None):
205
"""Copy all the versions for multiple file_ids from from_store.
207
:param from_transaction: required current transaction in from_store.
209
from bzrlib.transactions import PassThroughTransaction
210
if from_transaction is None:
211
warn("VersionedFileStore.copy_multi without a from_transaction parameter "
212
"is deprecated. Please provide a from_transaction.",
215
# we are reading one object - caching is irrelevant.
216
from_transaction = PassThroughTransaction()
217
if to_transaction is None:
218
warn("VersionedFileStore.copy_multi without a to_transaction parameter "
219
"is deprecated. Please provide a to_transaction.",
222
# we are copying single objects, and there may be open transactions
223
# so again with the passthrough
224
to_transaction = PassThroughTransaction()
225
pb = bzrlib.ui.ui_factory.nested_progress_bar()
227
for count, f in enumerate(file_ids):
228
mutter("copy weave {%s} into %s", f, self)
229
pb.update('copy', count, len(file_ids))
230
# if we have it in cache, its faster.
231
# joining is fast with knits, and bearable for weaves -
232
# indeed the new case can be optimised if needed.
233
target = self._make_new_versionedfile(f, to_transaction)
234
source = from_store.get_weave(f, from_transaction)
235
target.insert_record_stream(source.get_record_stream(
236
[(version,) for version in source.versions()],
237
'topological', False))
241
def total_size(self):
242
count, bytes = super(VersionedFileStore, self).total_size()
243
return (count / len(self._versionedfile_class.get_suffixes())), bytes
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):
123
assert isinstance(from_store, WeaveStore)
125
mutter("copy weave {%s} into %s", f, self)
126
self._put(f, from_store._get(f))