~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/store/weave.py

- refactor handling of short option names

Show diffs side-by-side

added added

removed removed

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