~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-03-08 00:37:41 UTC
  • mfrom: (1594.2.4 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060308003741-08afccbf89005e87
Merge in :
 * Knit repositories use knits
 * Nested progress bar support.
 * Ghost aware graph api.

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:
 
104
        if weave is not None:
80
105
            mutter("cache hit in %s for %s", self, file_id)
81
106
            return weave
82
 
        w = read_weave(self._get(file_id))
 
107
        w = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode)
83
108
        transaction.map.add_weave(file_id, w)
84
109
        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
 
 
 
110
        return w
 
111
 
 
112
    @deprecated_method(zero_eight)
112
113
    def get_lines(self, file_id, rev_id, transaction):
113
114
        """Return text from a particular version of a weave.
114
115
 
115
 
        Returned as a list of lines."""
 
116
        Returned as a list of lines.
 
117
        """
 
118
        assert 0
116
119
        w = self.get_weave(file_id, transaction)
117
 
        return w.get(w.lookup(rev_id))
 
120
        return w.get_lines(rev_id)
118
121
    
119
 
    def get_weave_prelude_or_empty(self, file_id, transaction):
120
 
        """cheap version that reads the prelude but not the lines
121
 
        """
 
122
    def _new_weave(self, file_id, transaction):
 
123
        """Make a new weave for file_id and return it."""
 
124
        weave = self._make_new_versionedfile(file_id, transaction)
 
125
        transaction.map.add_weave(file_id, weave)
 
126
        transaction.register_clean(weave, precious=self._precious)
 
127
        return weave
 
128
 
 
129
    def _make_new_versionedfile(self, file_id, transaction):
 
130
        if self.has_id(file_id):
 
131
            self.delete(file_id, transaction)
122
132
        try:
123
 
            return self.get_weave_prelude(file_id, transaction)
 
133
            weave = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode, create=True)
124
134
        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
 
135
            if not self._prefixed:
 
136
                # unexpected error - NoSuchFile is raised on a missing dir only and that
 
137
                # only occurs when we are prefixed.
 
138
                raise
 
139
            self._transport.mkdir(hash_prefix(file_id), mode=self._dir_mode)
 
140
            weave = self._versionedfile_class(self.filename(file_id), self._transport, self._file_mode, create=True)
 
141
        return weave
131
142
 
132
143
    def get_weave_or_empty(self, file_id, transaction):
133
144
        """Return a weave, or an empty one if it doesn't exist.""" 
134
145
        try:
135
146
            return self.get_weave(file_id, transaction)
136
147
        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
 
148
            return self._new_weave(file_id, transaction)
141
149
 
 
150
    @deprecated_method(zero_eight)
142
151
    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
 
 
 
152
        """This is a deprecated API: It writes an entire collection of ids out.
 
153
        
 
154
        This became inappropriate when we made a versioned file api which
 
155
        tracks the state of the collection of versions for a single id.
 
156
        
 
157
        Its maintained for backwards compatability but will only work on
 
158
        weave stores - pre 0.8 repositories.
 
159
        """
 
160
        self._put_weave(self, file_id, weave, transaction)
 
161
 
 
162
    def _put_weave(self, file_id, weave, transaction):
 
163
        """Preserved here for upgrades-to-weaves to use."""
 
164
        myweave = self._make_new_versionedfile(file_id, transaction)
 
165
        myweave.join(weave)
 
166
 
 
167
    @deprecated_method(zero_eight)
152
168
    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)
 
169
        """This method was a shorthand for 
 
170
 
 
171
        vfile = self.get_weave_or_empty(file_id, transaction)
 
172
        vfile.add_lines(rev_id, parents, new_lines)
 
173
        """
 
174
        vfile = self.get_weave_or_empty(file_id, transaction)
 
175
        vfile.add_lines(rev_id, parents, new_lines)
157
176
        
 
177
    @deprecated_method(zero_eight)
158
178
    def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
159
179
                           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):
 
180
        """This method was a shorthand for
 
181
 
 
182
        vfile = self.get_weave_or_empty(file_id, transaction)
 
183
        vfile.clone_text(new_rev_id, old_rev_id, parents)
 
184
        """
 
185
        vfile = self.get_weave_or_empty(file_id, transaction)
 
186
        vfile.clone_text(new_rev_id, old_rev_id, parents)
 
187
 
 
188
    def copy(self, source, result_id, transaction):
 
189
        """Copy the source versioned file to result_id in this store."""
 
190
        self._clear_cache_id(result_id, transaction)
 
191
        source.copy_to(self.filename(result_id), self._transport)
 
192
 
 
193
    def copy_all_ids(self, store_from, pb=None, from_transaction=None,
 
194
                     to_transaction=None):
 
195
        """Copy all the file ids from store_from into self."""
 
196
        if from_transaction is None:
 
197
            warn("Please pass from_transaction into "
 
198
                 "versioned_store.copy_all_ids.", stacklevel=2)
 
199
        if to_transaction is None:
 
200
            warn("Please pass to_transaction into "
 
201
                 "versioned_store.copy_all_ids.", stacklevel=2)
 
202
        if not store_from.listable():
 
203
            raise UnlistableStore(store_from)
 
204
        ids = []
 
205
        for count, file_id in enumerate(store_from):
 
206
            if pb:
 
207
                pb.update('listing files', count, count)
 
208
            ids.append(file_id)
 
209
        if pb:
 
210
            pb.clear()
 
211
        mutter('copy_all ids: %r', ids)
 
212
        self.copy_multi(store_from, ids, pb=pb,
 
213
                        from_transaction=from_transaction,
 
214
                        to_transaction=to_transaction)
 
215
 
 
216
    def copy_multi(self, from_store, file_ids, pb=None, from_transaction=None,
 
217
                   to_transaction=None):
 
218
        """Copy all the versions for multiple file_ids from from_store.
 
219
        
 
220
        :param from_transaction: required current transaction in from_store.
 
221
        """
 
222
        from bzrlib.transactions import PassThroughTransaction
166
223
        assert isinstance(from_store, WeaveStore)
 
224
        if from_transaction is None:
 
225
            warn("WeaveStore.copy_multi without a from_transaction parameter "
 
226
                 "is deprecated. Please provide a from_transaction.",
 
227
                 DeprecationWarning,
 
228
                 stacklevel=2)
 
229
            # we are reading one object - caching is irrelevant.
 
230
            from_transaction = PassThroughTransaction()
 
231
        if to_transaction is None:
 
232
            warn("WeaveStore.copy_multi without a to_transaction parameter "
 
233
                 "is deprecated. Please provide a to_transaction.",
 
234
                 DeprecationWarning,
 
235
                 stacklevel=2)
 
236
            # we are copying single objects, and there may be open tranasactions
 
237
            # so again with the passthrough
 
238
            to_transaction = PassThroughTransaction()
167
239
        for count, f in enumerate(file_ids):
168
240
            mutter("copy weave {%s} into %s", f, self)
169
241
            if pb:
170
242
                pb.update('copy', count, len(file_ids))
171
 
            self._put(f, from_store._get(f))
 
243
            # if we have it in cache, its faster.
 
244
            # joining is fast with knits, and bearable for weaves -
 
245
            # indeed the new case can be optimised if needed.
 
246
            target = self._make_new_versionedfile(f, to_transaction)
 
247
            target.join(from_store.get_weave(f, from_transaction))
172
248
        if pb:
173
249
            pb.clear()
 
250
 
 
251
    def total_size(self):
 
252
        count, bytes =  super(VersionedFileStore, self).total_size()
 
253
        return (count / len(self._versionedfile_class.get_suffixes())), bytes
 
254
 
 
255
WeaveStore = VersionedFileStore