~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/store/weave.py

Update news and readme

- better explanation of dependencies

Show diffs side-by-side

added added

removed removed

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