~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/store/versioned/__init__.py

MergeĀ fromĀ mainline

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
import urllib
25
25
 
26
26
from bzrlib.weavefile import read_weave, write_weave_v5
27
 
from bzrlib.weave import Weave
 
27
from bzrlib.weave import WeaveFile
28
28
from bzrlib.store import TransportStore, hash_prefix
29
29
from bzrlib.atomicfile import AtomicFile
30
30
from bzrlib.errors import NoSuchFile, FileExists
 
31
from bzrlib.symbol_versioning import *
31
32
from bzrlib.trace import mutter
32
33
 
33
34
 
34
 
class WeaveStore(TransportStore):
35
 
    """Collection of several weave files in a directory.
36
 
 
37
 
    This has some shortcuts for reading and writing them.
38
 
    """
39
 
    FILE_SUFFIX = '.weave'
 
35
class VersionedFileStore(TransportStore):
 
36
    """Collection of many versioned files in a transport."""
40
37
 
41
38
    def __init__(self, transport, prefixed=False, precious=False,
42
 
                 dir_mode=None, file_mode=None):
 
39
                 dir_mode=None, file_mode=None,
 
40
                 versionedfile_class=WeaveFile):
43
41
        super(WeaveStore, self).__init__(transport,
44
42
                dir_mode=dir_mode, file_mode=file_mode,
45
43
                prefixed=prefixed, compressed=False)
46
44
        self._precious = precious
 
45
        self._versionedfile_class = versionedfile_class
 
46
 
 
47
    def _clear_cache_id(self, file_id, transaction):
 
48
        """WARNING may lead to inconsistent object references for file_id.
 
49
 
 
50
        Remove file_id from the transaction map. 
 
51
 
 
52
        NOT in the transaction api because theres no reliable way to clear
 
53
        callers. So its here for very specialised use rather than having an
 
54
        'api' that isn't.
 
55
        """
 
56
        weave = transaction.map.find_weave(file_id)
 
57
        if weave is not None:
 
58
            mutter("old data in transaction in %s for %s", self, file_id)
 
59
            # FIXME abstraction violation - transaction now has stale data.
 
60
            transaction.map.remove_object(weave)
47
61
 
48
62
    def filename(self, file_id):
49
63
        """Return the path relative to the transport root."""
50
64
        if self._prefixed:
51
 
            return hash_prefix(file_id) + urllib.quote(file_id) + WeaveStore.FILE_SUFFIX
 
65
            return hash_prefix(file_id) + urllib.quote(file_id)
52
66
        else:
53
 
            return urllib.quote(file_id) + WeaveStore.FILE_SUFFIX
 
67
            return urllib.quote(file_id)
54
68
 
55
69
    def __iter__(self):
56
 
        l = len(WeaveStore.FILE_SUFFIX)
 
70
        suffixes = self._versionedfile_class.get_suffixes()
 
71
        ids = set()
57
72
        for relpath in self._iter_files_recursive():
58
 
            if relpath.endswith(WeaveStore.FILE_SUFFIX):
59
 
                yield os.path.basename(relpath[:-l])
 
73
            for suffix in suffixes:
 
74
                if relpath.endswith(suffix):
 
75
                    id = os.path.basename(relpath[:-len(suffix)])
 
76
                    if not id in ids:
 
77
                        yield id
 
78
                        ids.add(id)
60
79
 
61
80
    def has_id(self, fileid):
62
 
        return self._transport.has(self.filename(fileid))
63
 
 
64
 
    def _get(self, file_id):
65
 
        return self._transport.get(self.filename(file_id))
66
 
 
67
 
    def _put(self, file_id, f):
68
 
        # less round trips to mkdir on failure than mkdir always
69
 
        try:
70
 
            return self._transport.put(self.filename(file_id), f, mode=self._file_mode)
71
 
        except NoSuchFile:
72
 
            if not self._prefixed:
73
 
                raise
74
 
            self._transport.mkdir(hash_prefix(file_id), mode=self._dir_mode)
75
 
            return self._transport.put(self.filename(file_id), f, mode=self._file_mode)
 
81
        suffixes = self._versionedfile_class.get_suffixes()
 
82
        filename = self.filename(fileid)
 
83
        for suffix in suffixes:
 
84
            if not self._transport.has(filename + suffix):
 
85
                return False
 
86
        return True
 
87
 
 
88
    def get_empty(self, file_id, transaction):
 
89
        """Get an empty weave, which implies deleting the existing one first."""
 
90
        if self.has_id(file_id):
 
91
            self.delete(file_id, transaction)
 
92
        return self.get_weave_or_empty(file_id, transaction)
 
93
 
 
94
    def delete(self, file_id, transaction):
 
95
        """Remove file_id from the store."""
 
96
        suffixes = self._versionedfile_class.get_suffixes()
 
97
        filename = self.filename(file_id)
 
98
        for suffix in suffixes:
 
99
            self._transport.delete(filename + suffix)
 
100
        self._clear_cache_id(file_id, transaction)
76
101
 
77
102
    def get_weave(self, file_id, transaction):
78
103
        weave = transaction.map.find_weave(file_id)
79
 
        if weave:
80
 
            mutter("cache hit in %s for %s", self, file_id)
81
 
            return weave
82
 
        w = read_weave(self._get(file_id))
83
 
        transaction.map.add_weave(file_id, w)
84
 
        transaction.register_clean(w, precious=self._precious)
85
 
        # TODO: jam 20051219 This should check if there is a prelude
86
 
        #       which is already cached, and if so, should remove it
87
 
        #       But transaction doesn't seem to have a 'remove'
88
 
        #       One workaround would be to re-add the object with
89
 
        #       the PRELUDE marker.
90
 
        return w
91
 
 
92
 
    def get_weave_prelude(self, file_id, transaction):
93
 
        weave_id = file_id
94
 
        weave = transaction.map.find_weave(weave_id)
95
 
        if weave:
96
 
            mutter("cache hit in %s for %s", self, weave_id)
97
 
            return weave
98
 
        # We want transactions to also cache preludes if that
99
 
        # is all that we are loading. So we need a unique
100
 
        # identifier, so that someone who wants the whole text
101
 
        # won't get just the prelude
102
 
        weave_id = 'PRELUDE-' + file_id
103
 
        weave = transaction.map.find_weave(weave_id)
104
 
        if weave:
105
 
            mutter("cache hit in %s for %s", self, weave_id)
106
 
            return weave
107
 
        w = read_weave(self._get(file_id), prelude=True)
108
 
        transaction.map.add_weave(weave_id, w)
109
 
        transaction.register_clean(w, precious=self._precious)
110
 
        return w
111
 
 
 
104
        if weave is not None:
 
105
            #mutter("cache hit in %s for %s", self, file_id)
 
106
            return weave
 
107
        if transaction.writeable():
 
108
            w = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode)
 
109
            transaction.map.add_weave(file_id, w)
 
110
            transaction.register_dirty(w)
 
111
        else:
 
112
            w = self._versionedfile_class(self.filename(file_id),
 
113
                                          self._transport,
 
114
                                          self._file_mode,
 
115
                                          create=False,
 
116
                                          access_mode='r')
 
117
            transaction.map.add_weave(file_id, w)
 
118
            transaction.register_clean(w, precious=self._precious)
 
119
        return w
 
120
 
 
121
    @deprecated_method(zero_eight)
112
122
    def get_lines(self, file_id, rev_id, transaction):
113
123
        """Return text from a particular version of a weave.
114
124
 
115
 
        Returned as a list of lines."""
 
125
        Returned as a list of lines.
 
126
        """
 
127
        assert 0
116
128
        w = self.get_weave(file_id, transaction)
117
 
        return w.get(w.lookup(rev_id))
 
129
        return w.get_lines(rev_id)
118
130
    
119
 
    def get_weave_prelude_or_empty(self, file_id, transaction):
120
 
        """cheap version that reads the prelude but not the lines
121
 
        """
 
131
    def _new_weave(self, file_id, transaction):
 
132
        """Make a new weave for file_id and return it."""
 
133
        weave = self._make_new_versionedfile(file_id, transaction)
 
134
        transaction.map.add_weave(file_id, weave)
 
135
        # has to be dirty - its able to mutate on its own.
 
136
        transaction.register_dirty(weave)
 
137
        return weave
 
138
 
 
139
    def _make_new_versionedfile(self, file_id, transaction):
 
140
        if self.has_id(file_id):
 
141
            self.delete(file_id, transaction)
122
142
        try:
123
 
            return self.get_weave_prelude(file_id, transaction)
 
143
            weave = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode, create=True)
124
144
        except NoSuchFile:
125
 
            # We can cache here, because we know that there
126
 
            # is no complete object, since we got NoSuchFile
127
 
            weave = Weave(weave_name=file_id)
128
 
            transaction.map.add_weave(file_id, weave)
129
 
            transaction.register_clean(weave, precious=self._precious)
130
 
            return weave
 
145
            if not self._prefixed:
 
146
                # unexpected error - NoSuchFile is raised on a missing dir only and that
 
147
                # only occurs when we are prefixed.
 
148
                raise
 
149
            self._transport.mkdir(hash_prefix(file_id), mode=self._dir_mode)
 
150
            weave = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode, create=True)
 
151
        return weave
131
152
 
132
153
    def get_weave_or_empty(self, file_id, transaction):
133
154
        """Return a weave, or an empty one if it doesn't exist.""" 
134
155
        try:
135
156
            return self.get_weave(file_id, transaction)
136
157
        except NoSuchFile:
137
 
            weave = Weave(weave_name=file_id)
138
 
            transaction.map.add_weave(file_id, weave)
139
 
            transaction.register_clean(weave, precious=self._precious)
140
 
            return weave
 
158
            return self._new_weave(file_id, transaction)
141
159
 
 
160
    @deprecated_method(zero_eight)
142
161
    def put_weave(self, file_id, weave, transaction):
143
 
        """Write back a modified weave"""
144
 
        transaction.register_dirty(weave)
145
 
        # TODO FOR WRITE TRANSACTIONS: this should be done in a callback
146
 
        # from the transaction, when it decides to save.
147
 
        sio = StringIO()
148
 
        write_weave_v5(weave, sio)
149
 
        sio.seek(0)
150
 
        self._put(file_id, sio)
151
 
 
 
162
        """This is a deprecated API: It writes an entire collection of ids out.
 
163
        
 
164
        This became inappropriate when we made a versioned file api which
 
165
        tracks the state of the collection of versions for a single id.
 
166
        
 
167
        Its maintained for backwards compatability but will only work on
 
168
        weave stores - pre 0.8 repositories.
 
169
        """
 
170
        self._put_weave(self, file_id, weave, transaction)
 
171
 
 
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.join(weave)
 
176
 
 
177
    @deprecated_method(zero_eight)
152
178
    def add_text(self, file_id, rev_id, new_lines, parents, transaction):
153
 
        w = self.get_weave_or_empty(file_id, transaction)
154
 
        parent_idxs = map(w.lookup, parents)
155
 
        w.add(rev_id, parent_idxs, new_lines)
156
 
        self.put_weave(file_id, w, transaction)
 
179
        """This method was a shorthand for 
 
180
 
 
181
        vfile = self.get_weave_or_empty(file_id, transaction)
 
182
        vfile.add_lines(rev_id, parents, new_lines)
 
183
        """
 
184
        vfile = self.get_weave_or_empty(file_id, transaction)
 
185
        vfile.add_lines(rev_id, parents, new_lines)
157
186
        
 
187
    @deprecated_method(zero_eight)
158
188
    def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
159
189
                           transaction):
160
 
        w = self.get_weave_or_empty(file_id, transaction)
161
 
        parent_idxs = map(w.lookup, parents)
162
 
        w.add_identical(old_rev_id, new_rev_id, parent_idxs)
163
 
        self.put_weave(file_id, w, transaction)
164
 
     
165
 
    def copy_multi(self, from_store, file_ids, pb=None):
 
190
        """This method was a shorthand for
 
191
 
 
192
        vfile = self.get_weave_or_empty(file_id, transaction)
 
193
        vfile.clone_text(new_rev_id, old_rev_id, parents)
 
194
        """
 
195
        vfile = self.get_weave_or_empty(file_id, transaction)
 
196
        vfile.clone_text(new_rev_id, old_rev_id, parents)
 
197
 
 
198
    def copy(self, source, result_id, transaction):
 
199
        """Copy the source versioned file to result_id in this store."""
 
200
        self._clear_cache_id(result_id, transaction)
 
201
        source.copy_to(self.filename(result_id), self._transport)
 
202
 
 
203
    def copy_all_ids(self, store_from, pb=None, from_transaction=None,
 
204
                     to_transaction=None):
 
205
        """Copy all the file ids from store_from into self."""
 
206
        if from_transaction is None:
 
207
            warn("Please pass from_transaction into "
 
208
                 "versioned_store.copy_all_ids.", stacklevel=2)
 
209
        if to_transaction is None:
 
210
            warn("Please pass to_transaction into "
 
211
                 "versioned_store.copy_all_ids.", stacklevel=2)
 
212
        if not store_from.listable():
 
213
            raise UnlistableStore(store_from)
 
214
        ids = []
 
215
        for count, file_id in enumerate(store_from):
 
216
            if pb:
 
217
                pb.update('listing files', count, count)
 
218
            ids.append(file_id)
 
219
        if pb:
 
220
            pb.clear()
 
221
        mutter('copy_all ids: %r', ids)
 
222
        self.copy_multi(store_from, ids, pb=pb,
 
223
                        from_transaction=from_transaction,
 
224
                        to_transaction=to_transaction)
 
225
 
 
226
    def copy_multi(self, from_store, file_ids, pb=None, from_transaction=None,
 
227
                   to_transaction=None):
 
228
        """Copy all the versions for multiple file_ids from from_store.
 
229
        
 
230
        :param from_transaction: required current transaction in from_store.
 
231
        """
 
232
        from bzrlib.transactions import PassThroughTransaction
166
233
        assert isinstance(from_store, WeaveStore)
 
234
        if from_transaction is None:
 
235
            warn("WeaveStore.copy_multi without a from_transaction parameter "
 
236
                 "is deprecated. Please provide a from_transaction.",
 
237
                 DeprecationWarning,
 
238
                 stacklevel=2)
 
239
            # we are reading one object - caching is irrelevant.
 
240
            from_transaction = PassThroughTransaction()
 
241
        if to_transaction is None:
 
242
            warn("WeaveStore.copy_multi without a to_transaction parameter "
 
243
                 "is deprecated. Please provide a to_transaction.",
 
244
                 DeprecationWarning,
 
245
                 stacklevel=2)
 
246
            # we are copying single objects, and there may be open tranasactions
 
247
            # so again with the passthrough
 
248
            to_transaction = PassThroughTransaction()
167
249
        for count, f in enumerate(file_ids):
168
250
            mutter("copy weave {%s} into %s", f, self)
169
251
            if pb:
170
252
                pb.update('copy', count, len(file_ids))
171
 
            self._put(f, from_store._get(f))
 
253
            # if we have it in cache, its faster.
 
254
            # joining is fast with knits, and bearable for weaves -
 
255
            # indeed the new case can be optimised if needed.
 
256
            target = self._make_new_versionedfile(f, to_transaction)
 
257
            target.join(from_store.get_weave(f, from_transaction))
172
258
        if pb:
173
259
            pb.clear()
 
260
 
 
261
    def total_size(self):
 
262
        count, bytes =  super(VersionedFileStore, self).total_size()
 
263
        return (count / len(self._versionedfile_class.get_suffixes())), bytes
 
264
 
 
265
WeaveStore = VersionedFileStore