/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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
711 by Martin Pool
- store docs
17
"""
18
Stores are the main data-storage mechanism for Bazaar-NG.
1 by mbp at sourcefrog
import from baz patch-364
19
20
A store is a simple write-once container indexed by a universally
711 by Martin Pool
- store docs
21
unique ID.
22
"""
1 by mbp at sourcefrog
import from baz patch-364
23
127 by mbp at sourcefrog
- store support for retrieving compressed files
24
import os, tempfile, types, osutils, gzip, errno
81 by mbp at sourcefrog
show space usage for various stores in the info command
25
from stat import ST_SIZE
1 by mbp at sourcefrog
import from baz patch-364
26
from StringIO import StringIO
974.1.44 by aaron.bentley at utoronto
Added test of double-add in ImmutableStore
27
from bzrlib.errors import BzrError
1104 by Martin Pool
- Add a simple UIFactory
28
from bzrlib.trace import mutter
29
import bzrlib.ui
1092.2.2 by Robert Collins
move RemoteStore to store.py
30
from bzrlib.remotebranch import get_url
1 by mbp at sourcefrog
import from baz patch-364
31
32
######################################################################
33
# stores
34
35
class StoreError(Exception):
36
    pass
37
38
1092.2.1 by Robert Collins
minor refactors to store, create an ImmutableMemoryStore for testing or other such operations
39
class Store(object):
40
    """An abstract store that holds files indexed by unique names.
1 by mbp at sourcefrog
import from baz patch-364
41
42
    Files can be added, but not modified once they are in.  Typically
43
    the hash is used as the name, or something else known to be unique,
44
    such as a UUID.
45
46
    >>> st = ImmutableScratchStore()
47
48
    >>> st.add(StringIO('hello'), 'aa')
49
    >>> 'aa' in st
50
    True
51
    >>> 'foo' in st
52
    False
53
54
    You are not allowed to add an id that is already present.
55
56
    Entries can be retrieved as files, which may then be read.
57
58
    >>> st.add(StringIO('goodbye'), '123123')
59
    >>> st['123123'].read()
60
    'goodbye'
1092.2.1 by Robert Collins
minor refactors to store, create an ImmutableMemoryStore for testing or other such operations
61
    """
62
63
    def total_size(self):
64
        """Return (count, bytes)
65
66
        This is the (compressed) size stored on disk, not the size of
67
        the content."""
68
        total = 0
69
        count = 0
70
        for fid in self:
71
            count += 1
72
            total += self._item_size(fid)
73
        return count, total
74
75
76
class ImmutableStore(Store):
77
    """Store that stores files on disk.
1 by mbp at sourcefrog
import from baz patch-364
78
254 by Martin Pool
- Doc cleanups from Magnus Therning
79
    TODO: Atomic add by writing to a temporary file and renaming.
1092.2.1 by Robert Collins
minor refactors to store, create an ImmutableMemoryStore for testing or other such operations
80
    TODO: Guard against the same thing being stored twice, compressed and
81
          uncompressed during copy_multi_immutable - the window is for a
82
          matching store with some crack code that lets it offer a 
83
          non gz FOO and then a fz FOO.
1 by mbp at sourcefrog
import from baz patch-364
84
711 by Martin Pool
- store docs
85
    In bzr 0.0.5 and earlier, files within the store were marked
86
    readonly on disk.  This is no longer done but existing stores need
87
    to be accomodated.
1 by mbp at sourcefrog
import from baz patch-364
88
    """
89
90
    def __init__(self, basedir):
1092.2.1 by Robert Collins
minor refactors to store, create an ImmutableMemoryStore for testing or other such operations
91
        super(ImmutableStore, self).__init__()
1 by mbp at sourcefrog
import from baz patch-364
92
        self._basedir = basedir
93
94
    def _path(self, id):
712 by Martin Pool
- better check for invalid store ids
95
        if '\\' in id or '/' in id:
96
            raise ValueError("invalid store id %r" % id)
1 by mbp at sourcefrog
import from baz patch-364
97
        return os.path.join(self._basedir, id)
98
99
    def __repr__(self):
100
        return "%s(%r)" % (self.__class__.__name__, self._basedir)
101
129 by mbp at sourcefrog
Store.add defaults to adding gzipped files
102
    def add(self, f, fileid, compressed=True):
1 by mbp at sourcefrog
import from baz patch-364
103
        """Add contents of a file into the store.
104
254 by Martin Pool
- Doc cleanups from Magnus Therning
105
        f -- An open file, or file-like object."""
716 by Martin Pool
- write into store using AtomicFile
106
        # FIXME: Only works on files that will fit in memory
107
        
108
        from bzrlib.atomicfile import AtomicFile
109
        
1 by mbp at sourcefrog
import from baz patch-364
110
        mutter("add store entry %r" % (fileid))
111
        if isinstance(f, types.StringTypes):
112
            content = f
113
        else:
114
            content = f.read()
716 by Martin Pool
- write into store using AtomicFile
115
            
129 by mbp at sourcefrog
Store.add defaults to adding gzipped files
116
        p = self._path(fileid)
117
        if os.access(p, os.F_OK) or os.access(p + '.gz', os.F_OK):
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
118
            raise BzrError("store %r already contains id %r" % (self._basedir, fileid))
129 by mbp at sourcefrog
Store.add defaults to adding gzipped files
119
716 by Martin Pool
- write into store using AtomicFile
120
        fn = p
129 by mbp at sourcefrog
Store.add defaults to adding gzipped files
121
        if compressed:
716 by Martin Pool
- write into store using AtomicFile
122
            fn = fn + '.gz'
129 by mbp at sourcefrog
Store.add defaults to adding gzipped files
123
            
716 by Martin Pool
- write into store using AtomicFile
124
        af = AtomicFile(fn, 'wb')
125
        try:
126
            if compressed:
127
                gf = gzip.GzipFile(mode='wb', fileobj=af)
128
                gf.write(content)
129
                gf.close()
130
            else:
131
                af.write(content)
132
            af.commit()
133
        finally:
134
            af.close()
1 by mbp at sourcefrog
import from baz patch-364
135
670 by Martin Pool
- Show progress while branching
136
974.1.30 by aaron.bentley at utoronto
Changed copy_multi to permit failure and return a tuple, tested missing required revisions
137
    def copy_multi(self, other, ids, permit_failure=False):
626 by Martin Pool
- add Store.copy_multi for use in pulling changes into a branch
138
        """Copy texts for ids from other into self.
139
1116 by Martin Pool
- fix a few errors in new merge code
140
        If an id is present in self, it is skipped.
141
142
        Returns (count_copied, failed), where failed is a collection of ids
143
        that could not be copied.
626 by Martin Pool
- add Store.copy_multi for use in pulling changes into a branch
144
        """
1104 by Martin Pool
- Add a simple UIFactory
145
        pb = bzrlib.ui.ui_factory.progress_bar()
146
        
670 by Martin Pool
- Show progress while branching
147
        pb.update('preparing to copy')
148
        to_copy = [id for id in ids if id not in self]
790 by Martin Pool
Merge from aaron:
149
        if isinstance(other, ImmutableStore):
150
            return self.copy_multi_immutable(other, to_copy, pb)
626 by Martin Pool
- add Store.copy_multi for use in pulling changes into a branch
151
        count = 0
974.2.7 by aaron.bentley at utoronto
Merged from bzr.24
152
        failed = set()
670 by Martin Pool
- Show progress while branching
153
        for id in to_copy:
154
            count += 1
155
            pb.update('copy', count, len(to_copy))
974.1.30 by aaron.bentley at utoronto
Changed copy_multi to permit failure and return a tuple, tested missing required revisions
156
            if not permit_failure:
157
                self.add(other[id], id)
158
            else:
159
                try:
160
                    entry = other[id]
161
                except IndexError:
974.2.7 by aaron.bentley at utoronto
Merged from bzr.24
162
                    failed.add(id)
974.1.30 by aaron.bentley at utoronto
Changed copy_multi to permit failure and return a tuple, tested missing required revisions
163
                    continue
164
                self.add(entry, id)
165
                
974.2.7 by aaron.bentley at utoronto
Merged from bzr.24
166
        if not permit_failure:
167
            assert count == len(to_copy)
670 by Martin Pool
- Show progress while branching
168
        pb.clear()
974.2.7 by aaron.bentley at utoronto
Merged from bzr.24
169
        return count, failed
170
974.1.30 by aaron.bentley at utoronto
Changed copy_multi to permit failure and return a tuple, tested missing required revisions
171
    def copy_multi_immutable(self, other, to_copy, pb, permit_failure=False):
790 by Martin Pool
Merge from aaron:
172
        from shutil import copyfile
173
        count = 0
974.1.30 by aaron.bentley at utoronto
Changed copy_multi to permit failure and return a tuple, tested missing required revisions
174
        failed = set()
790 by Martin Pool
Merge from aaron:
175
        for id in to_copy:
176
            p = self._path(id)
177
            other_p = other._path(id)
178
            try:
179
                copyfile(other_p, p)
180
            except IOError, e:
181
                if e.errno == errno.ENOENT:
974.1.30 by aaron.bentley at utoronto
Changed copy_multi to permit failure and return a tuple, tested missing required revisions
182
                    if not permit_failure:
183
                        copyfile(other_p+".gz", p+".gz")
184
                    else:
185
                        try:
186
                            copyfile(other_p+".gz", p+".gz")
187
                        except IOError, e:
188
                            if e.errno == errno.ENOENT:
189
                                failed.add(id)
190
                            else:
191
                                raise
790 by Martin Pool
Merge from aaron:
192
                else:
193
                    raise
194
            
195
            count += 1
196
            pb.update('copy', count, len(to_copy))
197
        assert count == len(to_copy)
198
        pb.clear()
974.1.30 by aaron.bentley at utoronto
Changed copy_multi to permit failure and return a tuple, tested missing required revisions
199
        return count, failed
1 by mbp at sourcefrog
import from baz patch-364
200
201
    def __contains__(self, fileid):
202
        """"""
128 by mbp at sourcefrog
More support for compressed files in stores
203
        p = self._path(fileid)
204
        return (os.access(p, os.R_OK)
205
                or os.access(p + '.gz', os.R_OK))
1 by mbp at sourcefrog
import from baz patch-364
206
1092.2.1 by Robert Collins
minor refactors to store, create an ImmutableMemoryStore for testing or other such operations
207
    def _item_size(self, fid):
208
        p = self._path(fid)
209
        try:
210
            return os.stat(p)[ST_SIZE]
211
        except OSError:
212
            return os.stat(p + '.gz')[ST_SIZE]
1 by mbp at sourcefrog
import from baz patch-364
213
214
    def __iter__(self):
128 by mbp at sourcefrog
More support for compressed files in stores
215
        for f in os.listdir(self._basedir):
216
            if f[-3:] == '.gz':
217
                # TODO: case-insensitive?
218
                yield f[:-3]
219
            else:
220
                yield f
1 by mbp at sourcefrog
import from baz patch-364
221
80 by mbp at sourcefrog
show_info: Show number of entries in the branch stores
222
    def __len__(self):
223
        return len(os.listdir(self._basedir))
224
1 by mbp at sourcefrog
import from baz patch-364
225
    def __getitem__(self, fileid):
226
        """Returns a file reading from a particular entry."""
127 by mbp at sourcefrog
- store support for retrieving compressed files
227
        p = self._path(fileid)
228
        try:
229
            return gzip.GzipFile(p + '.gz', 'rb')
230
        except IOError, e:
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
231
            if e.errno != errno.ENOENT:
232
                raise
233
234
        try:
235
            return file(p, 'rb')
236
        except IOError, e:
237
            if e.errno != errno.ENOENT:
238
                raise
239
240
        raise IndexError(fileid)
241
1 by mbp at sourcefrog
import from baz patch-364
242
243
class ImmutableScratchStore(ImmutableStore):
244
    """Self-destructing test subclass of ImmutableStore.
245
246
    The Store only exists for the lifetime of the Python object.
711 by Martin Pool
- store docs
247
 Obviously you should not put anything precious in it.
1 by mbp at sourcefrog
import from baz patch-364
248
    """
249
    def __init__(self):
1092.2.1 by Robert Collins
minor refactors to store, create an ImmutableMemoryStore for testing or other such operations
250
        super(ImmutableScratchStore, self).__init__(tempfile.mkdtemp())
1 by mbp at sourcefrog
import from baz patch-364
251
252
    def __del__(self):
130 by mbp at sourcefrog
- fixup checks on retrieved files to cope with compression,
253
        for f in os.listdir(self._basedir):
163 by mbp at sourcefrog
merge win32 portability fixes
254
            fpath = os.path.join(self._basedir, f)
255
            # needed on windows, and maybe some other filesystems
256
            os.chmod(fpath, 0600)
257
            os.remove(fpath)
130 by mbp at sourcefrog
- fixup checks on retrieved files to cope with compression,
258
        os.rmdir(self._basedir)
259
        mutter("%r destroyed" % self)
1092.2.1 by Robert Collins
minor refactors to store, create an ImmutableMemoryStore for testing or other such operations
260
261
262
class ImmutableMemoryStore(Store):
263
    """A memory only store."""
264
265
    def __init__(self):
266
        super(ImmutableMemoryStore, self).__init__()
267
        self._contents = {}
268
269
    def add(self, stream, fileid, compressed=True):
270
        if self._contents.has_key(fileid):
271
            raise StoreError("fileid %s already in the store" % fileid)
272
        self._contents[fileid] = stream.read()
273
274
    def __getitem__(self, fileid):
275
        """Returns a file reading from a particular entry."""
276
        if not self._contents.has_key(fileid):
277
            raise IndexError
278
        return StringIO(self._contents[fileid])
279
280
    def _item_size(self, fileid):
281
        return len(self._contents[fileid])
282
283
    def __iter__(self):
284
        return iter(self._contents.keys())
1092.2.2 by Robert Collins
move RemoteStore to store.py
285
286
287
class RemoteStore(object):
288
289
    def __init__(self, baseurl):
290
        self._baseurl = baseurl
291
292
    def _path(self, name):
293
        if '/' in name:
294
            raise ValueError('invalid store id', name)
295
        return self._baseurl + '/' + name
296
        
297
    def __getitem__(self, fileid):
298
        p = self._path(fileid)
299
        try:
300
            return get_url(p, compressed=True)
301
        except:
302
            raise KeyError(fileid)