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
30
from . import TransportStore
31
from ....trace import mutter
34
class VersionedFileStore(TransportStore):
35
"""Collection of many versioned files in a transport."""
37
# TODO: Rather than passing versionedfile_kwargs, perhaps pass in a
38
# transport factory callable?
39
def __init__(self, transport, prefixed=False, precious=False,
40
dir_mode=None, file_mode=None,
41
versionedfile_class=None,
42
versionedfile_kwargs={},
44
super(VersionedFileStore, self).__init__(transport,
45
dir_mode=dir_mode, file_mode=file_mode,
46
prefixed=prefixed, compressed=False, escaped=escaped)
27
from bzrlib.weavefile import read_weave, write_weave_v5
28
from bzrlib.weave import Weave
29
from bzrlib.store import TransportStore, hash_prefix
30
from bzrlib.atomicfile import AtomicFile
31
from bzrlib.errors import NoSuchFile, FileExists
32
from bzrlib.trace import mutter
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
47
45
self._precious = precious
48
self._versionedfile_class = versionedfile_class
49
self._versionedfile_kwargs = versionedfile_kwargs
50
# Used for passing get_scope to versioned file constructors;
53
47
def filename(self, file_id):
54
48
"""Return the path relative to the transport root."""
55
return self._relpath(file_id)
50
return hash_prefix(file_id) + file_id + WeaveStore.FILE_SUFFIX
52
return file_id + WeaveStore.FILE_SUFFIX
57
54
def __iter__(self):
58
suffixes = self._versionedfile_class.get_suffixes()
60
for relpath in self._iter_files_recursive():
61
for suffix in suffixes:
62
if relpath.endswith(suffix):
63
# TODO: use standard remove_suffix function
64
escaped_id = os.path.basename(relpath[:-len(suffix)])
65
file_id = self._mapper.unmap(escaped_id)[0]
66
if file_id not in ids:
69
break # only one suffix can match
71
def has_id(self, file_id):
72
suffixes = self._versionedfile_class.get_suffixes()
73
filename = self.filename(file_id)
74
for suffix in suffixes:
75
if not self._transport.has(filename + suffix):
79
def get_empty(self, file_id, transaction):
80
"""Get an empty weave, which implies deleting the existing one first."""
81
if self.has_id(file_id):
82
self.delete(file_id, transaction)
83
return self.get_weave_or_empty(file_id, transaction)
85
def delete(self, file_id, transaction):
86
"""Remove file_id from the store."""
87
suffixes = self._versionedfile_class.get_suffixes()
88
filename = self.filename(file_id)
89
for suffix in suffixes:
90
self._transport.delete(filename + suffix)
55
l = len(WeaveStore.FILE_SUFFIX)
56
for relpath in self._transport.iter_files_recursive():
57
if relpath.endswith(WeaveStore.FILE_SUFFIX):
58
yield os.path.basename(relpath[:-l])
60
def has_id(self, fileid):
61
return self._transport.has(self.filename(fileid))
92
63
def _get(self, file_id):
93
64
return self._transport.get(self.filename(file_id))
95
66
def _put(self, file_id, f):
96
fn = self.filename(file_id)
98
return self._transport.put_file(fn, f, mode=self._file_mode)
99
except errors.NoSuchFile:
100
if not self._prefixed:
102
self._transport.mkdir(os.path.dirname(fn), mode=self._dir_mode)
103
return self._transport.put_file(fn, f, mode=self._file_mode)
105
def get_weave(self, file_id, transaction, _filename=None):
106
"""Return the VersionedFile for file_id.
108
:param _filename: filename that would be returned from self.filename for
109
file_id. This is used to reduce duplicate filename calculations when
110
using 'get_weave_or_empty'. FOR INTERNAL USE ONLY.
112
if _filename is None:
113
_filename = self.filename(file_id)
114
if transaction.writeable():
115
w = self._versionedfile_class(_filename, self._transport, self._file_mode,
116
get_scope=self.get_scope, **self._versionedfile_kwargs)
118
w = self._versionedfile_class(_filename,
123
get_scope=self.get_scope,
124
**self._versionedfile_kwargs)
69
self._transport.mkdir(hash_prefix(file_id))
72
return self._transport.put(self.filename(file_id), f)
74
def get_weave(self, file_id, transaction):
75
weave = transaction.map.find_weave(file_id)
77
mutter("cache hit in %s for %s", self, file_id)
79
w = read_weave(self._get(file_id))
80
transaction.map.add_weave(file_id, w)
81
transaction.register_clean(w, precious=self._precious)
127
def _make_new_versionedfile(self, file_id, transaction,
128
known_missing=False, _filename=None):
129
"""Make a new versioned file.
131
:param _filename: filename that would be returned from self.filename for
132
file_id. This is used to reduce duplicate filename calculations when
133
using 'get_weave_or_empty'. FOR INTERNAL USE ONLY.
135
if not known_missing and self.has_id(file_id):
136
self.delete(file_id, transaction)
137
if _filename is None:
138
_filename = self.filename(file_id)
140
# we try without making the directory first because thats optimising
141
# for the common case.
142
weave = self._versionedfile_class(_filename, self._transport, self._file_mode, create=True,
143
get_scope=self.get_scope, **self._versionedfile_kwargs)
144
except errors.NoSuchFile:
145
if not self._prefixed:
146
# unexpected error - NoSuchFile is expected to be raised on a
147
# missing dir only and that only occurs when we are prefixed.
149
dirname = osutils.dirname(_filename)
150
self._transport.mkdir(dirname, mode=self._dir_mode)
151
weave = self._versionedfile_class(_filename, self._transport,
152
self._file_mode, create=True,
153
get_scope=self.get_scope,
154
**self._versionedfile_kwargs)
84
def get_lines(self, file_id, rev_id, transaction):
85
"""Return text from a particular version of a weave.
87
Returned as a list of lines."""
88
w = self.get_weave(file_id, transaction)
89
return w.get(w.lookup(rev_id))
157
91
def get_weave_or_empty(self, file_id, transaction):
158
"""Return a weave, or an empty one if it doesn't exist."""
159
# This is typically used from 'commit' and 'fetch/push/pull' where
160
# we scan across many versioned files once. As such the small overhead
161
# of calculating the filename before doing a cache lookup is more than
162
# compensated for by not calculating the filename when making new
164
_filename = self.filename(file_id)
92
"""Return a weave, or an empty one if it doesn't exist."""
166
return self.get_weave(file_id, transaction, _filename=_filename)
167
except errors.NoSuchFile:
168
weave = self._make_new_versionedfile(file_id, transaction,
169
known_missing=True, _filename=_filename)
94
return self.get_weave(file_id, transaction)
96
weave = Weave(weave_name=file_id)
97
transaction.map.add_weave(file_id, weave)
98
transaction.register_clean(weave, precious=self._precious)
172
def _put_weave(self, file_id, weave, transaction):
173
"""Preserved here for upgrades-to-weaves to use."""
174
myweave = self._make_new_versionedfile(file_id, transaction)
175
myweave.insert_record_stream(weave.get_record_stream(
176
[(version,) for version in weave.versions()],
177
'topological', False))
101
def put_weave(self, file_id, weave, transaction):
102
"""Write back a modified weave"""
103
transaction.register_dirty(weave)
104
# TODO FOR WRITE TRANSACTIONS: this should be done in a callback
105
# from the transaction, when it decides to save.
107
write_weave_v5(weave, sio)
109
self._put(file_id, sio)
179
def total_size(self):
180
count, bytes = super(VersionedFileStore, self).total_size()
181
return (count / len(self._versionedfile_class.get_suffixes())), bytes
111
def add_text(self, file_id, rev_id, new_lines, parents, transaction):
112
w = self.get_weave_or_empty(file_id, transaction)
113
parent_idxs = map(w.lookup, parents)
114
w.add(rev_id, parent_idxs, new_lines)
115
self.put_weave(file_id, w, transaction)
117
def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
119
w = self.get_weave_or_empty(file_id, transaction)
120
parent_idxs = map(w.lookup, parents)
121
w.add_identical(old_rev_id, new_rev_id, parent_idxs)
122
self.put_weave(file_id, w, transaction)
124
def copy_multi(self, from_store, file_ids):
125
assert isinstance(from_store, WeaveStore)
127
mutter("copy weave {%s} into %s", f, self)
128
self._put(f, from_store._get(f))