/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

Fix formatting, remove catch-all for exceptions when opening local repositories.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
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
 
 
17
 
"""Tree classes, representing directory at point in time.
18
 
"""
19
 
 
20
 
from sets import Set
21
 
import os.path, os, fnmatch, time
22
 
 
23
 
from osutils import pumpfile, filesize, quotefn, sha_file, \
24
 
     joinpath, splitpath, appendpath, isdir, isfile, file_kind, fingerprint_file
25
 
import errno
26
 
from stat import S_ISREG, S_ISDIR, ST_MODE, ST_SIZE
27
 
 
28
 
from inventory import Inventory
29
 
from trace import mutter, note
30
 
from errors import bailout
31
 
import branch
32
 
 
33
 
import bzrlib
34
 
 
35
 
exporters = {}
36
 
 
37
 
class Tree(object):
38
 
    """Abstract file tree.
39
 
 
40
 
    There are several subclasses:
41
 
    
42
 
    * `WorkingTree` exists as files on disk editable by the user.
43
 
 
44
 
    * `RevisionTree` is a tree as recorded at some point in the past.
45
 
 
46
 
    * `EmptyTree`
47
 
 
48
 
    Trees contain an `Inventory` object, and also know how to retrieve
49
 
    file texts mentioned in the inventory, either from a working
50
 
    directory or from a store.
51
 
 
52
 
    It is possible for trees to contain files that are not described
53
 
    in their inventory or vice versa; for this use `filenames()`.
54
 
 
55
 
    Trees can be compared, etc, regardless of whether they are working
56
 
    trees or versioned trees.
57
 
    """
58
 
    
59
 
    def has_filename(self, filename):
60
 
        """True if the tree has given filename."""
61
 
        raise NotImplementedError()
62
 
 
63
 
    def has_id(self, file_id):
64
 
        return self.inventory.has_id(file_id)
65
 
 
66
 
    __contains__ = has_id
67
 
 
68
 
    def __iter__(self):
69
 
        return iter(self.inventory)
70
 
 
71
 
    def id2path(self, file_id):
72
 
        return self.inventory.id2path(file_id)
73
 
 
74
 
    def _get_inventory(self):
75
 
        return self._inventory
76
 
 
77
 
    inventory = property(_get_inventory,
78
 
                         doc="Inventory of this Tree")
79
 
 
80
 
    def _check_retrieved(self, ie, f):
81
 
        fp = fingerprint_file(f)
82
 
        f.seek(0)
83
 
        
84
 
        if ie.text_size != None:
85
 
            if ie.text_size != fp['size']:
86
 
                bailout("mismatched size for file %r in %r" % (ie.file_id, self._store),
87
 
                        ["inventory expects %d bytes" % ie.text_size,
88
 
                         "file is actually %d bytes" % fp['size'],
89
 
                         "store is probably damaged/corrupt"])
90
 
 
91
 
        if ie.text_sha1 != fp['sha1']:
92
 
            bailout("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
93
 
                    ["inventory expects %s" % ie.text_sha1,
94
 
                     "file is actually %s" % fp['sha1'],
95
 
                     "store is probably damaged/corrupt"])
96
 
 
97
 
 
98
 
    def print_file(self, fileid):
99
 
        """Print file with id `fileid` to stdout."""
100
 
        import sys
101
 
        pumpfile(self.get_file(fileid), sys.stdout)
102
 
        
103
 
        
104
 
    def export(self, dest, format='dir'):
105
 
        """Export this tree."""
106
 
        try:
107
 
            exporter = exporters[format]
108
 
        except KeyError:
109
 
            raise BzrCommandError("export format %r not supported" % format)
110
 
        exporter(self, dest)
111
 
 
112
 
 
113
 
 
114
 
class RevisionTree(Tree):
115
 
    """Tree viewing a previous revision.
116
 
 
117
 
    File text can be retrieved from the text store.
118
 
 
119
 
    TODO: Some kind of `__repr__` method, but a good one
120
 
           probably means knowing the branch and revision number,
121
 
           or at least passing a description to the constructor.
122
 
    """
123
 
    
124
 
    def __init__(self, store, inv):
125
 
        self._store = store
126
 
        self._inventory = inv
127
 
 
128
 
    def get_file(self, file_id):
129
 
        ie = self._inventory[file_id]
130
 
        f = self._store[ie.text_id]
131
 
        mutter("  get fileid{%s} from %r" % (file_id, self))
132
 
        self._check_retrieved(ie, f)
133
 
        return f
134
 
 
135
 
    def get_file_size(self, file_id):
136
 
        return self._inventory[file_id].text_size
137
 
 
138
 
    def get_file_sha1(self, file_id):
139
 
        ie = self._inventory[file_id]
140
 
        return ie.text_sha1
141
 
 
142
 
    def has_filename(self, filename):
143
 
        return bool(self.inventory.path2id(filename))
144
 
 
145
 
    def list_files(self):
146
 
        # The only files returned by this are those from the version
147
 
        for path, entry in self.inventory.iter_entries():
148
 
            yield path, 'V', entry.kind, entry.file_id
149
 
 
150
 
 
151
 
class EmptyTree(Tree):
152
 
    def __init__(self):
153
 
        self._inventory = Inventory()
154
 
 
155
 
    def has_filename(self, filename):
156
 
        return False
157
 
 
158
 
    def list_files(self):
159
 
        if False:  # just to make it a generator
160
 
            yield None
161
 
    
162
 
 
163
 
 
164
 
######################################################################
165
 
# diff
166
 
 
167
 
# TODO: Merge these two functions into a single one that can operate
168
 
# on either a whole tree or a set of files.
169
 
 
170
 
# TODO: Return the diff in order by filename, not by category or in
171
 
# random order.  Can probably be done by lock-stepping through the
172
 
# filenames from both trees.
173
 
 
174
 
 
175
 
def file_status(filename, old_tree, new_tree):
176
 
    """Return single-letter status, old and new names for a file.
177
 
 
178
 
    The complexity here is in deciding how to represent renames;
179
 
    many complex cases are possible.
180
 
    """
181
 
    old_inv = old_tree.inventory
182
 
    new_inv = new_tree.inventory
183
 
    new_id = new_inv.path2id(filename)
184
 
    old_id = old_inv.path2id(filename)
185
 
 
186
 
    if not new_id and not old_id:
187
 
        # easy: doesn't exist in either; not versioned at all
188
 
        if new_tree.is_ignored(filename):
189
 
            return 'I', None, None
190
 
        else:
191
 
            return '?', None, None
192
 
    elif new_id:
193
 
        # There is now a file of this name, great.
194
 
        pass
195
 
    else:
196
 
        # There is no longer a file of this name, but we can describe
197
 
        # what happened to the file that used to have
198
 
        # this name.  There are two possibilities: either it was
199
 
        # deleted entirely, or renamed.
200
 
        assert old_id
201
 
        if new_inv.has_id(old_id):
202
 
            return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
203
 
        else:
204
 
            return 'D', old_inv.id2path(old_id), None
205
 
 
206
 
    # if the file_id is new in this revision, it is added
207
 
    if new_id and not old_inv.has_id(new_id):
208
 
        return 'A'
209
 
 
210
 
    # if there used to be a file of this name, but that ID has now
211
 
    # disappeared, it is deleted
212
 
    if old_id and not new_inv.has_id(old_id):
213
 
        return 'D'
214
 
 
215
 
    return 'wtf?'
216
 
 
217
 
    
218
 
 
219
 
def find_renames(old_inv, new_inv):
220
 
    for file_id in old_inv:
221
 
        if file_id not in new_inv:
222
 
            continue
223
 
        old_name = old_inv.id2path(file_id)
224
 
        new_name = new_inv.id2path(file_id)
225
 
        if old_name != new_name:
226
 
            yield (old_name, new_name)
227
 
            
228
 
 
229
 
 
230
 
######################################################################
231
 
# export
232
 
 
233
 
def dir_exporter(tree, dest):
234
 
    """Export this tree to a new directory.
235
 
 
236
 
    `dest` should not exist, and will be created holding the
237
 
    contents of this tree.
238
 
 
239
 
    TODO: To handle subdirectories we need to create the
240
 
           directories first.
241
 
 
242
 
    :note: If the export fails, the destination directory will be
243
 
           left in a half-assed state.
244
 
    """
245
 
    os.mkdir(dest)
246
 
    mutter('export version %r' % tree)
247
 
    inv = tree.inventory
248
 
    for dp, ie in inv.iter_entries():
249
 
        kind = ie.kind
250
 
        fullpath = appendpath(dest, dp)
251
 
        if kind == 'directory':
252
 
            os.mkdir(fullpath)
253
 
        elif kind == 'file':
254
 
            pumpfile(tree.get_file(ie.file_id), file(fullpath, 'wb'))
255
 
        else:
256
 
            bailout("don't know how to export {%s} of kind %r" % (ie.file_id, kind))
257
 
        mutter("  export {%s} kind %s to %s" % (ie.file_id, kind, fullpath))
258
 
exporters['dir'] = dir_exporter
259
 
 
260
 
try:
261
 
    import tarfile
262
 
except ImportError:
263
 
    pass
264
 
else:
265
 
    def tar_exporter(tree, dest, compression=None):
266
 
        """Export this tree to a new tar file.
267
 
 
268
 
        `dest` will be created holding the contents of this tree; if it
269
 
        already exists, it will be clobbered, like with "tar -c".
270
 
        """
271
 
        now = time.time()
272
 
        compression = str(compression or '')
273
 
        try:
274
 
            ball = tarfile.open(dest, 'w:' + compression)
275
 
        except tarfile.CompressionError, e:
276
 
            bailout(str(e))
277
 
        mutter('export version %r' % tree)
278
 
        inv = tree.inventory
279
 
        for dp, ie in inv.iter_entries():
280
 
            mutter("  export {%s} kind %s to %s" % (ie.file_id, ie.kind, dest))
281
 
            item = tarfile.TarInfo(dp)
282
 
            # TODO: would be cool to actually set it to the timestamp of the
283
 
            # revision it was last changed
284
 
            item.mtime = now
285
 
            if ie.kind == 'directory':
286
 
                item.type = tarfile.DIRTYPE
287
 
                fileobj = None
288
 
                item.name += '/'
289
 
                item.size = 0
290
 
                item.mode = 0755
291
 
            elif ie.kind == 'file':
292
 
                item.type = tarfile.REGTYPE
293
 
                fileobj = tree.get_file(ie.file_id)
294
 
                item.size = _find_file_size(fileobj)
295
 
                item.mode = 0644
296
 
            else:
297
 
                bailout("don't know how to export {%s} of kind %r" %
298
 
                        (ie.file_id, ie.kind))
299
 
 
300
 
            ball.addfile(item, fileobj)
301
 
        ball.close()
302
 
    exporters['tar'] = tar_exporter
303
 
 
304
 
    def tgz_exporter(tree, dest):
305
 
        tar_exporter(tree, dest, compression='gz')
306
 
    exporters['tgz'] = tgz_exporter
307
 
 
308
 
    def tbz_exporter(tree, dest):
309
 
        tar_exporter(tree, dest, compression='bz2')
310
 
    exporters['tbz2'] = tbz_exporter
311
 
 
312
 
 
313
 
def _find_file_size(fileobj):
314
 
    offset = fileobj.tell()
315
 
    try:
316
 
        fileobj.seek(0, 2)
317
 
        size = fileobj.tell()
318
 
    except TypeError:
319
 
        # gzip doesn't accept second argument to seek()
320
 
        fileobj.seek(0)
321
 
        size = 0
322
 
        while True:
323
 
            nread = len(fileobj.read())
324
 
            if nread == 0:
325
 
                break
326
 
            size += nread
327
 
    fileobj.seek(offset)
328
 
    return size