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
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
19
# XXX: Some consideration of the problems that might occur if there are
18
20
# files whose id differs only in case. That should probably be forbidden.
22
from warnings import warn
25
from cStringIO import StringIO
28
from bzrlib.store import TransportStore
28
from bzrlib.weavefile import read_weave, write_weave_v5
29
from bzrlib.weave import Weave
30
from bzrlib.store import TransportStore, hash_prefix
31
from bzrlib.atomicfile import AtomicFile
32
from bzrlib.errors import NoSuchFile, FileExists
29
33
from bzrlib.trace import mutter
33
class VersionedFileStore(TransportStore):
34
"""Collection of many versioned files in a transport."""
36
# TODO: Rather than passing versionedfile_kwargs, perhaps pass in a
37
# transport factory callable?
38
def __init__(self, transport, prefixed=False, precious=False,
39
dir_mode=None, file_mode=None,
40
versionedfile_class=None,
41
versionedfile_kwargs={},
43
super(VersionedFileStore, self).__init__(transport,
44
dir_mode=dir_mode, file_mode=file_mode,
45
prefixed=prefixed, compressed=False, escaped=escaped)
36
class WeaveStore(TransportStore):
37
"""Collection of several weave files in a directory.
39
This has some shortcuts for reading and writing them.
41
FILE_SUFFIX = '.weave'
43
def __init__(self, transport, prefixed=False, precious=False):
44
self._transport = transport
45
self._prefixed = prefixed
46
46
self._precious = precious
47
self._versionedfile_class = versionedfile_class
48
self._versionedfile_kwargs = versionedfile_kwargs
49
# Used for passing get_scope to versioned file constructors;
52
48
def filename(self, file_id):
53
49
"""Return the path relative to the transport root."""
54
return self._relpath(file_id)
51
return hash_prefix(file_id) + urllib.quote(file_id) + WeaveStore.FILE_SUFFIX
53
return urllib.quote(file_id) + WeaveStore.FILE_SUFFIX
56
55
def __iter__(self):
57
suffixes = self._versionedfile_class.get_suffixes()
56
l = len(WeaveStore.FILE_SUFFIX)
59
57
for relpath in self._iter_files_recursive():
60
for suffix in suffixes:
61
if relpath.endswith(suffix):
62
# TODO: use standard remove_suffix function
63
escaped_id = os.path.basename(relpath[:-len(suffix)])
64
file_id = self._mapper.unmap(escaped_id)[0]
65
if file_id not in ids:
68
break # only one suffix can match
70
def has_id(self, file_id):
71
suffixes = self._versionedfile_class.get_suffixes()
72
filename = self.filename(file_id)
73
for suffix in suffixes:
74
if not self._transport.has(filename + suffix):
78
def get_empty(self, file_id, transaction):
79
"""Get an empty weave, which implies deleting the existing one first."""
80
if self.has_id(file_id):
81
self.delete(file_id, transaction)
82
return self.get_weave_or_empty(file_id, transaction)
84
def delete(self, file_id, transaction):
85
"""Remove file_id from the store."""
86
suffixes = self._versionedfile_class.get_suffixes()
87
filename = self.filename(file_id)
88
for suffix in suffixes:
89
self._transport.delete(filename + suffix)
58
if relpath.endswith(WeaveStore.FILE_SUFFIX):
59
yield os.path.basename(relpath[:-l])
61
def has_id(self, fileid):
62
return self._transport.has(self.filename(fileid))
91
64
def _get(self, file_id):
92
65
return self._transport.get(self.filename(file_id))
94
67
def _put(self, file_id, f):
95
fn = self.filename(file_id)
97
return self._transport.put_file(fn, f, mode=self._file_mode)
98
except errors.NoSuchFile:
99
if not self._prefixed:
101
self._transport.mkdir(os.path.dirname(fn), mode=self._dir_mode)
102
return self._transport.put_file(fn, f, mode=self._file_mode)
104
def get_weave(self, file_id, transaction, _filename=None):
105
"""Return the VersionedFile for file_id.
107
:param _filename: filename that would be returned from self.filename for
108
file_id. This is used to reduce duplicate filename calculations when
109
using 'get_weave_or_empty'. FOR INTERNAL USE ONLY.
111
if _filename is None:
112
_filename = self.filename(file_id)
113
if transaction.writeable():
114
w = self._versionedfile_class(_filename, self._transport, self._file_mode,
115
get_scope=self.get_scope, **self._versionedfile_kwargs)
117
w = self._versionedfile_class(_filename,
122
get_scope=self.get_scope,
123
**self._versionedfile_kwargs)
70
self._transport.mkdir(hash_prefix(file_id))
73
return self._transport.put(self.filename(file_id), f)
75
def get_weave(self, file_id, transaction):
76
weave = transaction.map.find_weave(file_id)
78
mutter("cache hit in %s for %s", self, file_id)
80
w = read_weave(self._get(file_id))
81
transaction.map.add_weave(file_id, w)
82
transaction.register_clean(w, precious=self._precious)
126
def _make_new_versionedfile(self, file_id, transaction,
127
known_missing=False, _filename=None):
128
"""Make a new versioned file.
130
:param _filename: filename that would be returned from self.filename for
131
file_id. This is used to reduce duplicate filename calculations when
132
using 'get_weave_or_empty'. FOR INTERNAL USE ONLY.
134
if not known_missing and self.has_id(file_id):
135
self.delete(file_id, transaction)
136
if _filename is None:
137
_filename = self.filename(file_id)
139
# we try without making the directory first because thats optimising
140
# for the common case.
141
weave = self._versionedfile_class(_filename, self._transport, self._file_mode, create=True,
142
get_scope=self.get_scope, **self._versionedfile_kwargs)
143
except errors.NoSuchFile:
144
if not self._prefixed:
145
# unexpected error - NoSuchFile is expected to be raised on a
146
# missing dir only and that only occurs when we are prefixed.
148
dirname = osutils.dirname(_filename)
149
self._transport.mkdir(dirname, mode=self._dir_mode)
150
weave = self._versionedfile_class(_filename, self._transport,
151
self._file_mode, create=True,
152
get_scope=self.get_scope,
153
**self._versionedfile_kwargs)
85
def get_lines(self, file_id, rev_id, transaction):
86
"""Return text from a particular version of a weave.
88
Returned as a list of lines."""
89
w = self.get_weave(file_id, transaction)
90
return w.get(w.lookup(rev_id))
156
92
def get_weave_or_empty(self, file_id, transaction):
157
"""Return a weave, or an empty one if it doesn't exist."""
158
# This is typically used from 'commit' and 'fetch/push/pull' where
159
# we scan across many versioned files once. As such the small overhead
160
# of calculating the filename before doing a cache lookup is more than
161
# compensated for by not calculating the filename when making new
163
_filename = self.filename(file_id)
93
"""Return a weave, or an empty one if it doesn't exist."""
165
return self.get_weave(file_id, transaction, _filename=_filename)
166
except errors.NoSuchFile:
167
weave = self._make_new_versionedfile(file_id, transaction,
168
known_missing=True, _filename=_filename)
95
return self.get_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)
171
def _put_weave(self, file_id, weave, transaction):
172
"""Preserved here for upgrades-to-weaves to use."""
173
myweave = self._make_new_versionedfile(file_id, transaction)
174
myweave.insert_record_stream(weave.get_record_stream(
175
[(version,) for version in weave.versions()],
176
'topological', False))
178
def copy_all_ids(self, store_from, pb=None, from_transaction=None,
179
to_transaction=None):
180
"""Copy all the file ids from store_from into self."""
181
if from_transaction is None:
182
warn("Please pass from_transaction into "
183
"versioned_store.copy_all_ids.", stacklevel=2)
184
if to_transaction is None:
185
warn("Please pass to_transaction into "
186
"versioned_store.copy_all_ids.", stacklevel=2)
187
if not store_from.listable():
188
raise errors.UnlistableStore(store_from)
190
for count, file_id in enumerate(store_from):
192
pb.update('listing files', count, count)
196
mutter('copy_all ids: %r', ids)
197
self.copy_multi(store_from, ids, pb=pb,
198
from_transaction=from_transaction,
199
to_transaction=to_transaction)
201
def copy_multi(self, from_store, file_ids, pb=None, from_transaction=None,
202
to_transaction=None):
203
"""Copy all the versions for multiple file_ids from from_store.
205
:param from_transaction: required current transaction in from_store.
207
from bzrlib.transactions import PassThroughTransaction
208
if from_transaction is None:
209
warn("VersionedFileStore.copy_multi without a from_transaction parameter "
210
"is deprecated. Please provide a from_transaction.",
213
# we are reading one object - caching is irrelevant.
214
from_transaction = PassThroughTransaction()
215
if to_transaction is None:
216
warn("VersionedFileStore.copy_multi without a to_transaction parameter "
217
"is deprecated. Please provide a to_transaction.",
220
# we are copying single objects, and there may be open tranasactions
221
# so again with the passthrough
222
to_transaction = PassThroughTransaction()
223
pb = bzrlib.ui.ui_factory.nested_progress_bar()
225
for count, f in enumerate(file_ids):
226
mutter("copy weave {%s} into %s", f, self)
227
pb.update('copy', count, len(file_ids))
228
# if we have it in cache, its faster.
229
# joining is fast with knits, and bearable for weaves -
230
# indeed the new case can be optimised if needed.
231
target = self._make_new_versionedfile(f, to_transaction)
232
source = from_store.get_weave(f, from_transaction)
233
target.insert_record_stream(source.get_record_stream(
234
[(version,) for version in source.versions()],
235
'topological', False))
239
def total_size(self):
240
count, bytes = super(VersionedFileStore, self).total_size()
241
return (count / len(self._versionedfile_class.get_suffixes())), bytes
102
def put_weave(self, file_id, weave, transaction):
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)
112
def add_text(self, file_id, rev_id, new_lines, parents, transaction):
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)
118
def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
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):
126
assert isinstance(from_store, WeaveStore)
128
mutter("copy weave {%s} into %s", f, self)
129
self._put(f, from_store._get(f))