~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tree.py

  • Committer: mbp at sourcefrog
  • Date: 2005-04-04 13:10:26 UTC
  • Revision ID: mbp@sourcefrog.net-20050404131026-628553cc03687658
new 'renames' command

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
21
20
from sets import Set
22
21
import os.path, os, fnmatch
23
22
 
 
23
from osutils import pumpfile, compare_files, 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
 
24
28
from inventory import Inventory
25
29
from trace import mutter, note
26
 
from osutils import pumpfile, compare_files, filesize, quotefn, sha_file, \
27
 
     joinpath, splitpath, appendpath, isdir, isfile, file_kind
28
30
from errors import bailout
29
31
import branch
30
 
from stat import S_ISREG, S_ISDIR, ST_MODE, ST_SIZE
31
32
 
32
33
import bzrlib
33
34
 
74
75
                         doc="Inventory of this Tree")
75
76
 
76
77
    def _check_retrieved(self, ie, f):
77
 
        # TODO: Test this check by damaging the store?
 
78
        fp = fingerprint_file(f)
 
79
        f.seek(0)
 
80
        
78
81
        if ie.text_size is not None:
79
 
            fs = filesize(f)
80
 
            if fs != ie.text_size:
 
82
            if ie.text_size != fp['size']:
81
83
                bailout("mismatched size for file %r in %r" % (ie.file_id, self._store),
82
84
                        ["inventory expects %d bytes" % ie.text_size,
83
 
                         "file is actually %d bytes" % fs,
 
85
                         "file is actually %d bytes" % fp['size'],
84
86
                         "store is probably damaged/corrupt"])
85
87
 
86
 
        f_hash = sha_file(f)
87
 
        f.seek(0)
88
 
        if ie.text_sha1 != f_hash:
 
88
        if ie.text_sha1 != fp['sha1']:
89
89
            bailout("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
90
90
                    ["inventory expects %s" % ie.text_sha1,
91
 
                     "file is actually %s" % f_hash,
 
91
                     "file is actually %s" % fp['sha1'],
92
92
                     "store is probably damaged/corrupt"])
93
93
 
94
94
 
138
138
        return "<%s of %s>" % (self.__class__.__name__,
139
139
                               self.basedir)
140
140
 
141
 
    def _rel(self, filename):
 
141
    def abspath(self, filename):
142
142
        return os.path.join(self.basedir, filename)
143
143
 
144
144
    def has_filename(self, filename):
145
 
        return os.path.exists(self._rel(filename))
 
145
        return os.path.exists(self.abspath(filename))
146
146
 
147
147
    def get_file(self, file_id):
148
148
        return self.get_file_byname(self.id2path(file_id))
149
149
 
150
150
    def get_file_byname(self, filename):
151
 
        return file(self._rel(filename), 'rb')
 
151
        return file(self.abspath(filename), 'rb')
152
152
 
153
153
    def _get_store_filename(self, file_id):
154
 
        return self._rel(self.id2path(file_id))
 
154
        return self.abspath(self.id2path(file_id))
155
155
 
156
156
    def has_id(self, file_id):
157
157
        # files that have been deleted are excluded
158
158
        if not self.inventory.has_id(file_id):
159
159
            return False
160
 
        return os.access(self._rel(self.inventory.id2path(file_id)), os.F_OK)
 
160
        return os.access(self.abspath(self.inventory.id2path(file_id)), os.F_OK)
161
161
 
162
162
    def get_file_size(self, file_id):
163
163
        return os.stat(self._get_store_filename(file_id))[ST_SIZE]
176
176
            return '?'
177
177
 
178
178
 
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
179
    def list_files(self):
189
180
        """Recursively list all files as (path, class, kind, id).
190
181
 
201
192
            ls = os.listdir(dp)
202
193
            ls.sort()
203
194
            for f in ls:
 
195
                ## TODO: If we find a subdirectory with its own .bzr
 
196
                ## directory, then that is a separate tree and we
 
197
                ## should exclude it.
204
198
                if bzrlib.BZRDIR == f:
205
199
                    continue
206
200
 
242
236
            
243
237
 
244
238
 
245
 
    def unknowns(self, path='', dir_id=None):
246
 
        """Yield names of unknown files in this WorkingTree.
 
239
    def unknowns(self):
 
240
        for subp in self.extras():
 
241
            if not self.is_ignored(subp):
 
242
                yield subp
 
243
 
 
244
 
 
245
    def extras(self):
 
246
        """Yield all unknown files in this WorkingTree.
247
247
 
248
248
        If there are any unknown directories then only the directory is
249
249
        returned, not all its children.  But if there are unknown files
251
251
 
252
252
        Currently returned depth-first, sorted by name within directories.
253
253
        """
254
 
        for fpath, fclass, fkind, fid in self.list_files():
255
 
            if fclass == '?':
256
 
                yield fpath
257
 
                
 
254
        ## TODO: Work from given directory downwards
 
255
        
 
256
        for path, dir_entry in self.inventory.directories():
 
257
            mutter("search for unknowns in %r" % path)
 
258
            dirabs = self.abspath(path)
 
259
            if not isdir(dirabs):
 
260
                # e.g. directory deleted
 
261
                continue
 
262
 
 
263
            fl = []
 
264
            for subf in os.listdir(dirabs):
 
265
                if (subf != '.bzr'
 
266
                    and (subf not in dir_entry.children)):
 
267
                    fl.append(subf)
 
268
            
 
269
            fl.sort()
 
270
            for subf in fl:
 
271
                subp = appendpath(path, subf)
 
272
                yield subp
 
273
 
258
274
 
259
275
    def ignored_files(self):
260
 
        for fpath, fclass, fkind, fid in self.list_files():
261
 
            if fclass == 'I':
262
 
                yield fpath
 
276
        """Yield list of PATH, IGNORE_PATTERN"""
 
277
        for subp in self.extras():
 
278
            pat = self.is_ignored(subp)
 
279
            if pat != None:
 
280
                yield subp, pat
263
281
 
264
282
 
265
283
    def get_ignore_list(self):
266
 
        """Return list of ignore patterns."""
 
284
        """Return list of ignore patterns.
 
285
 
 
286
        Cached in the Tree object after the first call.
 
287
        """
 
288
        if hasattr(self, '_ignorelist'):
 
289
            return self._ignorelist
 
290
 
 
291
        l = bzrlib.DEFAULT_IGNORE[:]
267
292
        if self.has_filename(bzrlib.IGNORE_FILENAME):
268
293
            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
 
294
            l.extend([line.rstrip("\n\r") for line in f.readlines()])
 
295
        self._ignorelist = l
 
296
        return l
272
297
 
273
298
 
274
299
    def is_ignored(self, filename):
275
300
        """Check whether the filename matches an ignore pattern.
276
301
 
277
302
        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?
 
303
        match against only the last component.
 
304
 
 
305
        If the file is ignored, returns the pattern which caused it to
 
306
        be ignored, otherwise None.  So this can simply be used as a
 
307
        boolean if desired."""
 
308
 
 
309
        ## TODO: Use '**' to match directories, and other extended globbing stuff from cvs/rsync.
 
310
        
282
311
        for pat in self.get_ignore_list():
283
312
            if '/' in pat:
284
 
                if fnmatch.fnmatchcase(filename, pat):
285
 
                    return True
 
313
                # as a special case, you can put ./ at the start of a pattern;
 
314
                # this is good to match in the top-level only;
 
315
                if pat[:2] == './':
 
316
                    newpat = pat[2:]
 
317
                else:
 
318
                    newpat = pat
 
319
                if fnmatch.fnmatchcase(filename, newpat):
 
320
                    return pat
286
321
            else:
287
322
                if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
288
 
                    return True
289
 
        return False
 
323
                    return pat
 
324
        return None
290
325
        
291
326
 
292
327
        
310
345
        ie = self._inventory[file_id]
311
346
        f = self._store[ie.text_id]
312
347
        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
348
        self._check_retrieved(ie, f)
317
349
        return f
318
350
 
400
432
 
401
433
    
402
434
 
 
435
def find_renames(old_inv, new_inv):
 
436
    for file_id in old_inv:
 
437
        if file_id not in new_inv:
 
438
            continue
 
439
        old_name = old_inv.id2path(file_id)
 
440
        new_name = new_inv.id2path(file_id)
 
441
        if old_name != new_name:
 
442
            yield (old_name, new_name)
 
443