~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/weavestore.py

  • Committer: Martin Pool
  • Date: 2005-09-19 07:43:53 UTC
  • Revision ID: mbp@sourcefrog.net-20050919074353-534837a6b802ce32
- compute order to import revisions

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
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
16
18
 
17
 
# XXX: Some consideration of the problems that might occur if there are
18
 
# files whose id differs only in case.  That should probably be forbidden.
19
 
 
20
 
 
 
19
# Author: Martin Pool <mbp@canonical.com>
 
20
 
 
21
 
 
22
import os
21
23
import errno
22
 
import os
23
 
from cStringIO import StringIO
24
 
import urllib
25
24
 
26
25
from bzrlib.weavefile import read_weave, write_weave_v5
27
 
from bzrlib.weave import WeaveFile, Weave
28
 
from bzrlib.store import TransportStore
 
26
from bzrlib.weave import Weave
29
27
from bzrlib.atomicfile import AtomicFile
30
 
from bzrlib.errors import NoSuchFile, FileExists
31
 
from bzrlib.symbol_versioning import *
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)
49
 
        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)
 
28
 
 
29
 
 
30
class WeaveStore(object):
 
31
    """Collection of several weave files."""
 
32
    def __init__(self, dir):
 
33
        self._dir = dir
 
34
 
67
35
 
68
36
    def filename(self, file_id):
69
 
        """Return the path relative to the transport root."""
70
 
        return self._relpath(file_id)
71
 
 
72
 
    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)
107
 
 
108
 
    def _get(self, file_id):
109
 
        return self._transport.get(self.filename(file_id))
110
 
 
111
 
    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)
120
 
 
121
 
    def get_weave(self, file_id, transaction):
122
 
        weave = transaction.map.find_weave(file_id)
123
 
        if weave is not None:
124
 
            #mutter("cache hit in %s for %s", self, file_id)
125
 
            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)
140
 
        return w
141
 
 
142
 
    @deprecated_method(zero_eight)
143
 
    def get_lines(self, file_id, rev_id, transaction):
 
37
        return self._dir + os.sep + file_id + '.weave'
 
38
 
 
39
 
 
40
    def get_weave(self, file_id):
 
41
        return read_weave(file(self.filename(file_id), 'rb'))
 
42
 
 
43
 
 
44
    def get_lines(self, file_id, rev_id):
144
45
        """Return text from a particular version of a weave.
145
46
 
146
 
        Returned as a list of lines.
147
 
        """
148
 
        w = self.get_weave(file_id, transaction)
149
 
        return w.get_lines(rev_id)
 
47
        Returned as a list of lines."""
 
48
        w = self.get_weave(file_id)
 
49
        return w.get(w.lookup(rev_id))
150
50
    
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
 
    def get_weave_or_empty(self, file_id, transaction):
 
51
 
 
52
    def get_weave_or_empty(self, file_id):
177
53
        """Return a weave, or an empty one if it doesn't exist.""" 
178
54
        try:
179
 
            return self.get_weave(file_id, transaction)
180
 
        except NoSuchFile:
181
 
            return self._new_weave(file_id, transaction)
182
 
 
183
 
    @deprecated_method(zero_eight)
184
 
    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)
201
 
    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)
209
 
        
210
 
    @deprecated_method(zero_eight)
211
 
    def add_identical_text(self, file_id, old_rev_id, new_rev_id, parents,
212
 
                           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
256
 
        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):
274
 
            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
 
55
            inf = file(self.filename(file_id), 'rb')
 
56
        except IOError, e:
 
57
            if e.errno == errno.ENOENT:
 
58
                return Weave(weave_name=file_id)
 
59
            else:
 
60
                raise
 
61
        else:
 
62
            return read_weave(inf)
 
63
    
 
64
 
 
65
    def put_weave(self, file_id, weave):
 
66
        """Write back a modified weave"""
 
67
        weave_fn = self.filename(file_id)
 
68
        af = AtomicFile(weave_fn)
 
69
        try:
 
70
            write_weave_v5(weave, af)
 
71
            af.commit()
 
72
        finally:
 
73
            af.close()
 
74
 
 
75
 
 
76
    def add_text(self, file_id, rev_id, new_lines, parents):
 
77
        w = self.get_weave_or_empty(file_id)
 
78
        parent_idxs = map(w.lookup, parents)
 
79
        w.add(rev_id, parent_idxs, new_lines)
 
80
        self.put_weave(file_id, w)
 
81
        
 
82