~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/store/weave.py

- add short-form of testaments

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
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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, 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))
97
63
 
98
64
    def _get(self, file_id):
99
65
        return self._transport.get(self.filename(file_id))
100
66
 
101
67
    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)
 
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)
 
74
 
 
75
    def get_weave(self, file_id, transaction):
 
76
        weave = transaction.map.find_weave(file_id)
 
77
        if weave:
 
78
            mutter("cache hit in %s for %s", self, file_id)
 
79
            return weave
 
80
        w = read_weave(self._get(file_id))
 
81
        transaction.map.add_weave(file_id, w)
 
82
        transaction.register_clean(w, precious=self._precious)
131
83
        return w
132
84
 
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
 
85
    def get_lines(self, file_id, rev_id, transaction):
 
86
        """Return text from a particular version of a weave.
162
87
 
 
88
        Returned as a list of lines."""
 
89
        w = self.get_weave(file_id, transaction)
 
90
        return w.get(w.lookup(rev_id))
 
91
    
163
92
    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)
 
93
        """Return a weave, or an empty one if it doesn't exist.""" 
171
94
        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)
 
95
            return self.get_weave(file_id, transaction)
 
96
        except NoSuchFile:
 
97
            weave = Weave(weave_name=file_id)
 
98
            transaction.map.add_weave(file_id, weave)
 
99
            transaction.register_clean(weave, precious=self._precious)
176
100
            return weave
177
101
 
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.
 
102
    def put_weave(self, file_id, weave, transaction):
 
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
 
 
112
    def add_text(self, file_id, rev_id, new_lines, parents, transaction):
 
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)
211
117
        
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
 
118
    def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
 
119
                           transaction):
 
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):
 
126
        assert isinstance(from_store, WeaveStore)
 
127
        for f in file_ids:
 
128
            mutter("copy weave {%s} into %s", f, self)
 
129
            self._put(f, from_store._get(f))