~bzr-pqm/bzr/bzr.dev

711 by Martin Pool
- store docs
1
# Copyright (C) 2005 by Canonical Development Ltd
1 by mbp at sourcefrog
import from baz patch-364
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
1374 by Martin Pool
todo
17
# TODO: Could remember a bias towards whether a particular store is typically
18
# compressed or not.
19
711 by Martin Pool
- store docs
20
"""
21
Stores are the main data-storage mechanism for Bazaar-NG.
1 by mbp at sourcefrog
import from baz patch-364
22
23
A store is a simple write-once container indexed by a universally
711 by Martin Pool
- store docs
24
unique ID.
25
"""
1 by mbp at sourcefrog
import from baz patch-364
26
1442.1.51 by Robert Collins
teach iter about suffixes
27
import os
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
28
from cStringIO import StringIO
1479 by Robert Collins
More quoting at the transport layer bugfixes.
29
import urllib
1429 by Robert Collins
merge in niemeyers prefixed-store patch
30
from zlib import adler32
1185.1.41 by Robert Collins
massive patch from Alexander Belchenko - many PEP8 fixes, removes unused function uuid
31
1442.1.44 by Robert Collins
Many transport related tweaks:
32
import bzrlib
1433 by Robert Collins
merge in and make incremental Gustavo Niemeyers nested log patch, and remove all bare exceptions in store and transport packages.
33
import bzrlib.errors as errors
1393.2.3 by John Arbash Meinel
Fixing typos, updating stores, getting tests to pass.
34
from bzrlib.errors import BzrError, UnlistableStore, TransportNotPossible
1104 by Martin Pool
- Add a simple UIFactory
35
from bzrlib.trace import mutter
1442.1.44 by Robert Collins
Many transport related tweaks:
36
import bzrlib.transport as transport
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
37
from bzrlib.transport.local import LocalTransport
1 by mbp at sourcefrog
import from baz patch-364
38
39
######################################################################
40
# stores
41
42
class StoreError(Exception):
43
    pass
44
45
1185.11.1 by John Arbash Meinel
(broken) Transport work is merged in. Tests do not pass yet.
46
class Store(object):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
47
    """This class represents the abstract storage layout for saving information.
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
48
    
1 by mbp at sourcefrog
import from baz patch-364
49
    Files can be added, but not modified once they are in.  Typically
50
    the hash is used as the name, or something else known to be unique,
51
    such as a UUID.
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
52
    """
53
54
    def __len__(self):
55
        raise NotImplementedError('Children should define their length')
56
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
57
    def get(self, fileid, suffix=None):
1442.1.50 by Robert Collins
test get with suffixes
58
        """Returns a file reading from a particular entry.
59
        
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
60
        If suffix is present, retrieve the named suffix for fileid.
1442.1.50 by Robert Collins
test get with suffixes
61
        """
62
        raise NotImplementedError
1442.1.35 by Robert Collins
convert all users of __getitem__ into TransportStores to use .get instead
63
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
64
    def __getitem__(self, fileid):
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
65
        """DEPRECATED. Please use .get(fileid) instead."""
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
66
        raise NotImplementedError
67
1442.1.45 by Robert Collins
replace __contains__ calls in stores with has_id
68
    #def __contains__(self, fileid):
69
    #    """Deprecated, please use has_id"""
70
    #    raise NotImplementedError
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
71
72
    def __iter__(self):
73
        raise NotImplementedError
74
907.1.43 by John Arbash Meinel
Restoring compatibility for Storage.add(file, fileid), it is a little arbitrary, and compatibility is better
75
    def add(self, f, fileid):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
76
        """Add a file object f to the store accessible from the given fileid"""
1185.11.1 by John Arbash Meinel
(broken) Transport work is merged in. Tests do not pass yet.
77
        raise NotImplementedError('Children of Store must define their method of adding entries.')
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
78
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
79
    def has_id(self, fileid, suffix=None):
80
        """Return True or false for the presence of fileid in the store.
1442.1.47 by Robert Collins
test for has with suffixed files
81
        
82
        suffix, if present, is a per file suffix, i.e. for digital signature 
83
        data."""
1442.1.45 by Robert Collins
replace __contains__ calls in stores with has_id
84
        raise NotImplementedError
907.1.36 by John Arbash Meinel
Moving the multi-get functionality higher up into the Branch class.
85
1400.1.1 by Robert Collins
implement a basic test for the ui branch command from http servers
86
    def listable(self):
87
        """Return True if this store is able to be listed."""
88
        return hasattr(self, "__iter__")
89
1185.11.1 by John Arbash Meinel
(broken) Transport work is merged in. Tests do not pass yet.
90
    def copy_multi(self, other, ids, pb=None, permit_failure=False):
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
91
        """Copy texts for ids from other into self.
92
93
        If an id is present in self, it is skipped.  A count of copied
94
        ids is returned, which may be less than len(ids).
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
95
1185.11.1 by John Arbash Meinel
(broken) Transport work is merged in. Tests do not pass yet.
96
        :param other: Another Store object
907.1.2 by John Arbash Meinel
Working on making Branch() do all of it's work over a Transport.
97
        :param ids: A list of entry ids to be copied
1185.11.1 by John Arbash Meinel
(broken) Transport work is merged in. Tests do not pass yet.
98
        :param pb: A ProgressBar object, if none is given, the default will be created.
99
        :param permit_failure: Allow missing entries to be ignored
100
        :return: (n_copied, [failed]) The number of entries copied successfully,
101
            followed by a list of entries which could not be copied (because they
102
            were missing)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
103
        """
1185.11.1 by John Arbash Meinel
(broken) Transport work is merged in. Tests do not pass yet.
104
        if pb is None:
105
            pb = bzrlib.ui.ui_factory.progress_bar()
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
106
        pb.update('preparing to copy')
974.2.7 by aaron.bentley at utoronto
Merged from bzr.24
107
        failed = set()
1442.1.53 by Robert Collins
Unroll the multiple-copy logic enough to remove the duplicate iteration and yet retain the optimised gzip->gzip copy.
108
        count = 0
109
        ids = list(ids) # get the list for showing a length.
110
        for fileid in ids:
111
            count += 1
112
            if self.has_id(fileid):
113
                continue
114
            try:
1442.1.54 by Robert Collins
Teach store.copy_all about fileid suffixes
115
                self._copy_one(fileid, None, other, pb)
116
                for suffix in self._suffixes:
117
                    try:
118
                        self._copy_one(fileid, suffix, other, pb)
119
                    except KeyError:
120
                        pass
1442.1.53 by Robert Collins
Unroll the multiple-copy logic enough to remove the duplicate iteration and yet retain the optimised gzip->gzip copy.
121
                pb.update('copy', count, len(ids))
122
            except KeyError:
123
                if permit_failure:
124
                    failed.add(fileid)
125
                else:
126
                    raise
127
        assert count == len(ids)
907.1.1 by John Arbash Meinel
Reworking the Branch and Store code to support an abstracted filesystem layer.
128
        pb.clear()
1442.1.53 by Robert Collins
Unroll the multiple-copy logic enough to remove the duplicate iteration and yet retain the optimised gzip->gzip copy.
129
        return count, failed
130
1442.1.54 by Robert Collins
Teach store.copy_all about fileid suffixes
131
    def _copy_one(self, fileid, suffix, other, pb):
1442.1.53 by Robert Collins
Unroll the multiple-copy logic enough to remove the duplicate iteration and yet retain the optimised gzip->gzip copy.
132
        """Most generic copy-one object routine.
133
        
134
        Subclasses can override this to provide an optimised
135
        copy between their own instances. Such overriden routines
136
        should call this if they have no optimised facility for a 
137
        specific 'other'.
138
        """
1185.16.159 by John Arbash Meinel
Updated the stores, all tests pass, and a store doesn't have to be 100% compressed
139
        mutter('Store._copy_one: %r', fileid)
1442.1.54 by Robert Collins
Teach store.copy_all about fileid suffixes
140
        f = other.get(fileid, suffix)
141
        self.add(f, fileid, suffix)
1185.10.1 by Aaron Bentley
Added --basis option to bzr branch
142
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
143
144
class TransportStore(Store):
145
    """A TransportStore is a Store superclass for Stores that use Transports."""
146
1442.1.33 by Robert Collins
teach TransportStore.add to accept an optional file suffix, which does not alter the fileid.
147
    def add(self, f, fileid, suffix=None):
1442.1.28 by Robert Collins
pull up core TransportStore.add from TextStore.add and CompressedTextStore.add
148
        """Add contents of a file into the store.
149
150
        f -- A file-like object, or string
151
        """
1185.16.159 by John Arbash Meinel
Updated the stores, all tests pass, and a store doesn't have to be 100% compressed
152
        mutter("add store entry %r", fileid)
1442.1.33 by Robert Collins
teach TransportStore.add to accept an optional file suffix, which does not alter the fileid.
153
        
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
154
        names = self._id_to_names(fileid, suffix)
155
        if self._transport.has_any(names):
156
            raise BzrError("store %r already contains id %r" 
157
                           % (self._transport.base, fileid))
1442.1.28 by Robert Collins
pull up core TransportStore.add from TextStore.add and CompressedTextStore.add
158
1185.16.159 by John Arbash Meinel
Updated the stores, all tests pass, and a store doesn't have to be 100% compressed
159
        # Most of the time, just adding the file will work
160
        # if we find a time where it fails, (because the dir
161
        # doesn't exist), then create the dir, and try again
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
162
        self._add(names[0], f)
163
1185.16.159 by John Arbash Meinel
Updated the stores, all tests pass, and a store doesn't have to be 100% compressed
164
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
165
    def _add(self, relpath, f):
166
        """Actually add the file to the given location.
167
        This should be overridden by children.
168
        """
169
        raise NotImplementedError('children need to implement this function.')
1442.1.28 by Robert Collins
pull up core TransportStore.add from TextStore.add and CompressedTextStore.add
170
1442.1.24 by Robert Collins
Pull up _check_id and _relpath from Text and CompressedText stores into TransportStore
171
    def _check_fileid(self, fileid):
172
        if not isinstance(fileid, basestring):
173
            raise TypeError('Fileids should be a string type: %s %r' % (type(fileid), fileid))
174
        if '\\' in fileid or '/' in fileid:
175
            raise ValueError("invalid store id %r" % fileid)
176
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
177
    def _id_to_names(self, fileid, suffix):
178
        """Return the names in the expected order"""
1442.1.47 by Robert Collins
test for has with suffixed files
179
        if suffix is not None:
180
            fn = self._relpath(fileid, [suffix])
181
        else:
182
            fn = self._relpath(fileid)
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
183
184
        fn_gz = fn + '.gz'
185
        if self._compressed:
186
            return fn_gz, fn
187
        else:
188
            return fn, fn_gz
189
190
    def has_id(self, fileid, suffix=None):
191
        """See Store.has_id."""
192
        return self._transport.has_any(self._id_to_names(fileid, suffix))
193
194
    def _get_name(self, fileid, suffix=None):
195
        """A special check, which returns the name of an existing file.
196
        
197
        This is similar in spirit to 'has_id', but it is designed
198
        to return information about which file the store has.
199
        """
200
        for name in self._id_to_names(fileid, suffix=suffix):
201
            if self._transport.has(name):
202
                return name
203
        return None
1442.1.38 by Robert Collins
unify __contains__ for TransportStore classes
204
1442.1.36 by Robert Collins
convert get() in TextStore and CompressedTextStore into a template method
205
    def _get(self, filename):
206
        """Return an vanilla file stream for clients to read from.
207
208
        This is the body of a template method on 'get', and should be 
209
        implemented by subclasses.
210
        """
211
        raise NotImplementedError
212
1442.1.50 by Robert Collins
test get with suffixes
213
    def get(self, fileid, suffix=None):
214
        """See Store.get()."""
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
215
        names = self._id_to_names(fileid, suffix)
216
        for name in names:
217
            try:
218
                return self._get(name)
219
            except errors.NoSuchFile:
220
                pass
221
        raise KeyError(fileid)
1433 by Robert Collins
merge in and make incremental Gustavo Niemeyers nested log patch, and remove all bare exceptions in store and transport packages.
222
1185.58.4 by John Arbash Meinel
Added permission checking to Branch, and propogated that change into the stores.
223
    def __init__(self, a_transport, prefixed=False, compressed=False,
224
                 dir_mode=None, file_mode=None):
1442.1.44 by Robert Collins
Many transport related tweaks:
225
        assert isinstance(a_transport, transport.Transport)
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
226
        super(TransportStore, self).__init__()
1442.1.44 by Robert Collins
Many transport related tweaks:
227
        self._transport = a_transport
1442.1.25 by Robert Collins
Test TransportStore._relpath for simple cases: pull up _prefixed attribute as a result.
228
        self._prefixed = prefixed
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
229
        self._compressed = compressed
1442.1.43 by Robert Collins
add registration of suffixes, in preparation for ensuring iteration is regular
230
        self._suffixes = set()
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
231
1185.58.6 by John Arbash Meinel
Stores don't have to have a dir_mode or file_mode set
232
        # It is okay for these to be None, it just means they
233
        # will just use the filesystem defaults
234
        self._dir_mode = dir_mode
235
        self._file_mode = file_mode
1185.58.4 by John Arbash Meinel
Added permission checking to Branch, and propogated that change into the stores.
236
1479 by Robert Collins
More quoting at the transport layer bugfixes.
237
    def _iter_files_recursive(self):
238
        """Iterate through the files in the transport."""
239
        for quoted_relpath in self._transport.iter_files_recursive():
240
            yield urllib.unquote(quoted_relpath)
241
1442.1.51 by Robert Collins
teach iter about suffixes
242
    def __iter__(self):
1479 by Robert Collins
More quoting at the transport layer bugfixes.
243
        for relpath in self._iter_files_recursive():
1442.1.51 by Robert Collins
teach iter about suffixes
244
            # worst case is one of each suffix.
245
            name = os.path.basename(relpath)
246
            if name.endswith('.gz'):
247
                name = name[:-3]
248
            skip = False
249
            for count in range(len(self._suffixes)):
250
                for suffix in self._suffixes:
251
                    if name.endswith('.' + suffix):
252
                        skip = True
253
            if not skip:
254
                yield name
255
1442.1.40 by Robert Collins
unify __len__() implementations for TransportStore classes
256
    def __len__(self):
1442.1.50 by Robert Collins
test get with suffixes
257
        return len(list(self.__iter__()))
1442.1.40 by Robert Collins
unify __len__() implementations for TransportStore classes
258
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
259
    def _relpath(self, fileid, suffixes=None):
1442.1.24 by Robert Collins
Pull up _check_id and _relpath from Text and CompressedText stores into TransportStore
260
        self._check_fileid(fileid)
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
261
        if suffixes:
262
            for suffix in suffixes:
263
                if not suffix in self._suffixes:
264
                    raise ValueError("Unregistered suffix %r" % suffix)
265
                self._check_fileid(suffix)
266
        else:
267
            suffixes = []
1442.1.24 by Robert Collins
Pull up _check_id and _relpath from Text and CompressedText stores into TransportStore
268
        if self._prefixed:
1442.1.26 by Robert Collins
Pull up _relpath with gz suffix for CompressedTextStore into TransportStore
269
            path = [hash_prefix(fileid) + fileid]
1442.1.24 by Robert Collins
Pull up _check_id and _relpath from Text and CompressedText stores into TransportStore
270
        else:
1442.1.26 by Robert Collins
Pull up _relpath with gz suffix for CompressedTextStore into TransportStore
271
            path = [fileid]
272
        path.extend(suffixes)
1185.33.66 by Martin Pool
[patch] use unicode literals for all hardcoded paths (Alexander Belchenko)
273
        return transport.urlescape(u'.'.join(path))
1442.1.24 by Robert Collins
Pull up _check_id and _relpath from Text and CompressedText stores into TransportStore
274
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
275
    def __repr__(self):
276
        if self._transport is None:
277
            return "%s(None)" % (self.__class__.__name__)
278
        else:
279
            return "%s(%r)" % (self.__class__.__name__, self._transport.base)
280
281
    __str__ = __repr__
1185.10.1 by Aaron Bentley
Added --basis option to bzr branch
282
1400.1.1 by Robert Collins
implement a basic test for the ui branch command from http servers
283
    def listable(self):
284
        """Return True if this store is able to be listed."""
285
        return self._transport.listable()
286
1442.1.43 by Robert Collins
add registration of suffixes, in preparation for ensuring iteration is regular
287
    def register_suffix(self, suffix):
288
        """Register a suffix as being expected in this store."""
289
        self._check_fileid(suffix)
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
290
        if suffix == 'gz':
291
            raise ValueError('You cannot register the "gz" suffix.')
1442.1.43 by Robert Collins
add registration of suffixes, in preparation for ensuring iteration is regular
292
        self._suffixes.add(suffix)
293
1442.1.37 by Robert Collins
pull up total_size into TransportStore
294
    def total_size(self):
295
        """Return (count, bytes)
296
297
        This is the (compressed) size stored on disk, not the size of
298
        the content."""
299
        total = 0
300
        count = 0
1442.1.44 by Robert Collins
Many transport related tweaks:
301
        for relpath in self._transport.iter_files_recursive():
1442.1.37 by Robert Collins
pull up total_size into TransportStore
302
            count += 1
1442.1.44 by Robert Collins
Many transport related tweaks:
303
            total += self._transport.stat(relpath).st_size
1442.1.37 by Robert Collins
pull up total_size into TransportStore
304
                
305
        return count, total
306
1092.2.1 by Robert Collins
minor refactors to store, create an ImmutableMemoryStore for testing or other such operations
307
1442.1.44 by Robert Collins
Many transport related tweaks:
308
def ImmutableMemoryStore():
309
    return bzrlib.store.text.TextStore(transport.memory.MemoryTransport())
1092.2.24 by Robert Collins
merge from martins newformat branch - brings in transport abstraction
310
        
311
1185.10.1 by Aaron Bentley
Added --basis option to bzr branch
312
def copy_all(store_from, store_to):
313
    """Copy all ids from one store to another."""
1393.1.14 by Martin Pool
doc
314
    # TODO: Optional progress indicator
1400.1.1 by Robert Collins
implement a basic test for the ui branch command from http servers
315
    if not store_from.listable():
316
        raise UnlistableStore(store_from)
317
    ids = [f for f in store_from]
1185.16.159 by John Arbash Meinel
Updated the stores, all tests pass, and a store doesn't have to be 100% compressed
318
    mutter('copy_all ids: %r', ids)
1185.10.1 by Aaron Bentley
Added --basis option to bzr branch
319
    store_to.copy_multi(store_from, ids)
1393.2.1 by John Arbash Meinel
Merged in split-storage-2 branch. Need to cleanup a little bit more still.
320
1185.16.157 by John Arbash Meinel
Added ability for TextStore to handle both compressed and uncompressed, it just looks for one type first
321
def hash_prefix(fileid):
322
    return "%02x/" % (adler32(fileid) & 0xff)
1429 by Robert Collins
merge in niemeyers prefixed-store patch
323