~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-30 22:27:17 UTC
  • Revision ID: mbp@sourcefrog.net-20050330222717-027b5837127b938d
experiment with new nested inventory file format
not used by default yet

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/env python
2
 
# -*- coding: UTF-8 -*-
 
1
# Copyright (C) 2005 Canonical Ltd
3
2
 
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
24
23
from inventory import Inventory
25
24
from trace import mutter, note
26
25
from osutils import pumpfile, compare_files, filesize, quotefn, sha_file, \
27
 
     joinpath, splitpath, appendpath, isdir, isfile, file_kind
 
26
     joinpath, splitpath, appendpath, isdir, isfile, file_kind, fingerprint_file
28
27
from errors import bailout
29
28
import branch
30
29
from stat import S_ISREG, S_ISDIR, ST_MODE, ST_SIZE
74
73
                         doc="Inventory of this Tree")
75
74
 
76
75
    def _check_retrieved(self, ie, f):
77
 
        # TODO: Test this check by damaging the store?
 
76
        fp = fingerprint_file(f)
 
77
        f.seek(0)
 
78
        
78
79
        if ie.text_size is not None:
79
 
            fs = filesize(f)
80
 
            if fs != ie.text_size:
 
80
            if ie.text_size != fp['size']:
81
81
                bailout("mismatched size for file %r in %r" % (ie.file_id, self._store),
82
82
                        ["inventory expects %d bytes" % ie.text_size,
83
 
                         "file is actually %d bytes" % fs,
 
83
                         "file is actually %d bytes" % fp['size'],
84
84
                         "store is probably damaged/corrupt"])
85
85
 
86
 
        f_hash = sha_file(f)
87
 
        f.seek(0)
88
 
        if ie.text_sha1 != f_hash:
 
86
        if ie.text_sha1 != fp['sha1']:
89
87
            bailout("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
90
88
                    ["inventory expects %s" % ie.text_sha1,
91
 
                     "file is actually %s" % f_hash,
 
89
                     "file is actually %s" % fp['sha1'],
92
90
                     "store is probably damaged/corrupt"])
93
91
 
94
92
 
138
136
        return "<%s of %s>" % (self.__class__.__name__,
139
137
                               self.basedir)
140
138
 
141
 
    def _rel(self, filename):
 
139
    def abspath(self, filename):
142
140
        return os.path.join(self.basedir, filename)
143
141
 
144
142
    def has_filename(self, filename):
145
 
        return os.path.exists(self._rel(filename))
 
143
        return os.path.exists(self.abspath(filename))
146
144
 
147
145
    def get_file(self, file_id):
148
146
        return self.get_file_byname(self.id2path(file_id))
149
147
 
150
148
    def get_file_byname(self, filename):
151
 
        return file(self._rel(filename), 'rb')
 
149
        return file(self.abspath(filename), 'rb')
152
150
 
153
151
    def _get_store_filename(self, file_id):
154
 
        return self._rel(self.id2path(file_id))
 
152
        return self.abspath(self.id2path(file_id))
155
153
 
156
154
    def has_id(self, file_id):
157
155
        # files that have been deleted are excluded
158
156
        if not self.inventory.has_id(file_id):
159
157
            return False
160
 
        return os.access(self._rel(self.inventory.id2path(file_id)), os.F_OK)
 
158
        return os.access(self.abspath(self.inventory.id2path(file_id)), os.F_OK)
161
159
 
162
160
    def get_file_size(self, file_id):
163
161
        return os.stat(self._get_store_filename(file_id))[ST_SIZE]
176
174
            return '?'
177
175
 
178
176
 
179
 
    def file_kind(self, filename):
180
 
        if isfile(self._rel(filename)):
181
 
            return 'file'
182
 
        elif isdir(self._rel(filename)):
183
 
            return 'directory'
184
 
        else:
185
 
            return 'unknown'
186
 
 
187
 
 
188
177
    def list_files(self):
189
178
        """Recursively list all files as (path, class, kind, id).
190
179
 
201
190
            ls = os.listdir(dp)
202
191
            ls.sort()
203
192
            for f in ls:
 
193
                ## TODO: If we find a subdirectory with its own .bzr
 
194
                ## directory, then that is a separate tree and we
 
195
                ## should exclude it.
204
196
                if bzrlib.BZRDIR == f:
205
197
                    continue
206
198
 
242
234
            
243
235
 
244
236
 
245
 
    def unknowns(self, path='', dir_id=None):
246
 
        """Yield names of unknown files in this WorkingTree.
 
237
    def unknowns(self):
 
238
        for subp in self.extras():
 
239
            if not self.is_ignored(subp):
 
240
                yield subp
 
241
 
 
242
 
 
243
    def extras(self):
 
244
        """Yield all unknown files in this WorkingTree.
247
245
 
248
246
        If there are any unknown directories then only the directory is
249
247
        returned, not all its children.  But if there are unknown files
251
249
 
252
250
        Currently returned depth-first, sorted by name within directories.
253
251
        """
254
 
        for fpath, fclass, fkind, fid in self.list_files():
255
 
            if fclass == '?':
256
 
                yield fpath
 
252
        ## TODO: Work from given directory downwards
 
253
        
 
254
        for path, dir_entry in self.inventory.directories():
 
255
            mutter("search for unknowns in %r" % path)
 
256
            dirabs = self.abspath(path)
 
257
            if not isdir(dirabs):
 
258
                # e.g. directory deleted
 
259
                continue
 
260
 
 
261
            fl = []
 
262
            for subf in os.listdir(dirabs):
 
263
                if (subf != '.bzr'
 
264
                    and (subf not in dir_entry.children)):
 
265
                    fl.append(subf)
 
266
            
 
267
            fl.sort()
 
268
            for subf in fl:
 
269
                subp = appendpath(path, subf)
 
270
                yield subp
257
271
                
258
272
 
259
273
    def ignored_files(self):
260
 
        for fpath, fclass, fkind, fid in self.list_files():
261
 
            if fclass == 'I':
262
 
                yield fpath
 
274
        """Yield list of PATH, IGNORE_PATTERN"""
 
275
        for subp in self.extras():
 
276
            pat = self.is_ignored(subp)
 
277
            if pat != None:
 
278
                yield subp, pat
263
279
 
264
280
 
265
281
    def get_ignore_list(self):
266
 
        """Return list of ignore patterns."""
 
282
        """Return list of ignore patterns.
 
283
 
 
284
        Cached in the Tree object after the first call.
 
285
        """
 
286
        if hasattr(self, '_ignorelist'):
 
287
            return self._ignorelist
 
288
 
 
289
        l = bzrlib.DEFAULT_IGNORE[:]
267
290
        if self.has_filename(bzrlib.IGNORE_FILENAME):
268
291
            f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
269
 
            return [line.rstrip("\n\r") for line in f.readlines()]
270
 
        else:
271
 
            return bzrlib.DEFAULT_IGNORE
 
292
            l.extend([line.rstrip("\n\r") for line in f.readlines()])
 
293
        self._ignorelist = l
 
294
        return l
272
295
 
273
296
 
274
297
    def is_ignored(self, filename):
275
298
        """Check whether the filename matches an ignore pattern.
276
299
 
277
300
        Patterns containing '/' need to match the whole path; others
278
 
        match against only the last component."""
279
 
        ## TODO: Take them from a file, not hardcoded
280
 
        ## TODO: Use extended zsh-style globs maybe?
281
 
        ## TODO: Use '**' to match directories?
 
301
        match against only the last component.
 
302
 
 
303
        If the file is ignored, returns the pattern which caused it to
 
304
        be ignored, otherwise None.  So this can simply be used as a
 
305
        boolean if desired."""
 
306
 
 
307
        ## TODO: Use '**' to match directories, and other extended globbing stuff from cvs/rsync.
 
308
        
282
309
        for pat in self.get_ignore_list():
283
310
            if '/' in pat:
284
 
                if fnmatch.fnmatchcase(filename, pat):
285
 
                    return True
 
311
                # as a special case, you can put ./ at the start of a pattern;
 
312
                # this is good to match in the top-level only;
 
313
                if pat[:2] == './':
 
314
                    newpat = pat[2:]
 
315
                else:
 
316
                    newpat = pat
 
317
                if fnmatch.fnmatchcase(filename, newpat):
 
318
                    return pat
286
319
            else:
287
320
                if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
288
 
                    return True
289
 
        return False
 
321
                    return pat
 
322
        return None
290
323
        
291
324
 
292
325
        
310
343
        ie = self._inventory[file_id]
311
344
        f = self._store[ie.text_id]
312
345
        mutter("  get fileid{%s} from %r" % (file_id, self))
313
 
        fs = filesize(f)
314
 
        if ie.text_size is None:
315
 
            note("warning: no text size recorded on %r" % ie)
316
346
        self._check_retrieved(ie, f)
317
347
        return f
318
348