~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Martin Pool
  • Date: 2005-05-12 02:14:35 UTC
  • Revision ID: mbp@sourcefrog.net-20050512021435-87fa19f051842647
- new helper function kind_marker()

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
18
 
 
19
 
# FIXME: I don't know if writing out the cache from the destructor is really a
20
 
# good idea, because destructors are considered poor taste in Python, and
21
 
# it's not predictable when it will be written out.
22
17
 
23
18
import os
24
 
import stat
25
 
import fnmatch
26
 
        
 
19
    
27
20
import bzrlib.tree
28
 
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath
29
 
from bzrlib.errors import BzrCheckError
30
 
from bzrlib.trace import mutter
31
 
 
32
 
class TreeEntry(object):
33
 
    """An entry that implements the minium interface used by commands.
34
 
 
35
 
    This needs further inspection, it may be better to have 
36
 
    InventoryEntries without ids - though that seems wrong. For now,
37
 
    this is a parallel hierarchy to InventoryEntry, and needs to become
38
 
    one of several things: decorates to that hierarchy, children of, or
39
 
    parents of it.
40
 
    Another note is that these objects are currently only used when there is
41
 
    no InventoryEntry available - i.e. for unversioned objects.
42
 
    Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
43
 
    """
44
 
 
45
 
    def __eq__(self, other):
46
 
        # yes, this us ugly, TODO: best practice __eq__ style.
47
 
        return (isinstance(other, TreeEntry)
48
 
                and other.__class__ == self.__class__)
49
 
 
50
 
    def kind_character(self):
51
 
        return "???"
52
 
 
53
 
 
54
 
class TreeDirectory(TreeEntry):
55
 
    """See TreeEntry. This is a directory in a working tree."""
56
 
 
57
 
    def __eq__(self, other):
58
 
        return (isinstance(other, TreeDirectory)
59
 
                and other.__class__ == self.__class__)
60
 
 
61
 
    def kind_character(self):
62
 
        return "/"
63
 
 
64
 
 
65
 
class TreeFile(TreeEntry):
66
 
    """See TreeEntry. This is a regular file in a working tree."""
67
 
 
68
 
    def __eq__(self, other):
69
 
        return (isinstance(other, TreeFile)
70
 
                and other.__class__ == self.__class__)
71
 
 
72
 
    def kind_character(self):
73
 
        return ''
74
 
 
75
 
 
76
 
class TreeLink(TreeEntry):
77
 
    """See TreeEntry. This is a symlink in a working tree."""
78
 
 
79
 
    def __eq__(self, other):
80
 
        return (isinstance(other, TreeLink)
81
 
                and other.__class__ == self.__class__)
82
 
 
83
 
    def kind_character(self):
84
 
        return ''
 
21
from errors import BzrCheckError
 
22
from trace import mutter
85
23
 
86
24
 
87
25
class WorkingTree(bzrlib.tree.Tree):
93
31
    It is possible for a `WorkingTree` to have a filename which is
94
32
    not listed in the Inventory and vice versa.
95
33
    """
 
34
    _statcache = None
 
35
    
96
36
    def __init__(self, basedir, inv):
97
 
        from bzrlib.hashcache import HashCache
98
 
        from bzrlib.trace import note, mutter
99
 
 
100
37
        self._inventory = inv
101
38
        self.basedir = basedir
102
39
        self.path2id = inv.path2id
103
40
 
104
 
        # update the whole cache up front and write to disk if anything changed;
105
 
        # in the future we might want to do this more selectively
106
 
        hc = self._hashcache = HashCache(basedir)
107
 
        hc.read()
108
 
        hc.scan()
109
 
 
110
 
        if hc.needs_write:
111
 
            mutter("write hc")
112
 
            hc.write()
113
 
            
114
 
            
115
 
    def __del__(self):
116
 
        if self._hashcache.needs_write:
117
 
            self._hashcache.write()
118
 
 
119
 
 
120
41
    def __iter__(self):
121
42
        """Iterate through file_ids for this tree.
122
43
 
123
44
        file_ids are in a WorkingTree if they are in the working inventory
124
45
        and the working file exists.
125
46
        """
 
47
        self._update_statcache()
126
48
        inv = self._inventory
127
 
        for path, ie in inv.iter_entries():
128
 
            if bzrlib.osutils.lexists(self.abspath(path)):
129
 
                yield ie.file_id
 
49
        for file_id in self._inventory:
 
50
            # TODO: This is slightly redundant; we should be able to just
 
51
            # check the statcache but it only includes regular files.
 
52
            # only include files which still exist on disk
 
53
            ie = inv[file_id]
 
54
            if ie.kind == 'file':
 
55
                if ((file_id in self._statcache)
 
56
                    or (os.path.exists(self.abspath(inv.id2path(file_id))))):
 
57
                    yield file_id
 
58
 
130
59
 
131
60
 
132
61
    def __repr__(self):
133
62
        return "<%s of %s>" % (self.__class__.__name__,
134
 
                               getattr(self, 'basedir', None))
135
 
 
136
 
 
 
63
                               self.basedir)
137
64
 
138
65
    def abspath(self, filename):
139
66
        return os.path.join(self.basedir, filename)
140
67
 
141
68
    def has_filename(self, filename):
142
 
        return bzrlib.osutils.lexists(self.abspath(filename))
 
69
        return os.path.exists(self.abspath(filename))
143
70
 
144
71
    def get_file(self, file_id):
145
72
        return self.get_file_byname(self.id2path(file_id))
151
78
        ## XXX: badly named; this isn't in the store at all
152
79
        return self.abspath(self.id2path(file_id))
153
80
 
154
 
 
155
 
    def id2abspath(self, file_id):
156
 
        return self.abspath(self.id2path(file_id))
157
 
 
158
81
                
159
82
    def has_id(self, file_id):
160
83
        # files that have been deleted are excluded
161
 
        inv = self._inventory
162
 
        if not inv.has_id(file_id):
 
84
        if not self.inventory.has_id(file_id):
163
85
            return False
164
 
        path = inv.id2path(file_id)
165
 
        return bzrlib.osutils.lexists(self.abspath(path))
 
86
        self._update_statcache()
 
87
        if file_id in self._statcache:
 
88
            return True
 
89
        return os.path.exists(self.abspath(self.id2path(file_id)))
166
90
 
167
91
 
168
92
    __contains__ = has_id
169
93
    
170
94
 
 
95
    def _update_statcache(self):
 
96
        import statcache
 
97
        if not self._statcache:
 
98
            self._statcache = statcache.update_cache(self.basedir, self.inventory)
 
99
 
171
100
    def get_file_size(self, file_id):
172
 
        return os.path.getsize(self.id2abspath(file_id))
 
101
        import os, stat
 
102
        return os.stat(self._get_store_filename(file_id))[stat.ST_SIZE]
 
103
 
173
104
 
174
105
    def get_file_sha1(self, file_id):
175
 
        path = self._inventory.id2path(file_id)
176
 
        return self._hashcache.get_sha1(path)
177
 
 
178
 
 
179
 
    def is_executable(self, file_id):
180
 
        if os.name == "nt":
181
 
            return self._inventory[file_id].executable
182
 
        else:
183
 
            path = self._inventory.id2path(file_id)
184
 
            mode = os.lstat(self.abspath(path)).st_mode
185
 
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
186
 
 
187
 
    def get_symlink_target(self, file_id):
188
 
        return os.readlink(self.id2abspath(file_id))
 
106
        import statcache
 
107
        self._update_statcache()
 
108
        return self._statcache[file_id][statcache.SC_SHA1]
 
109
 
189
110
 
190
111
    def file_class(self, filename):
191
112
        if self.path2id(filename):
206
127
 
207
128
        Skips the control directory.
208
129
        """
209
 
        inv = self._inventory
 
130
        from osutils import appendpath, file_kind
 
131
        import os
 
132
 
 
133
        inv = self.inventory
210
134
 
211
135
        def descend(from_dir_relpath, from_dir_id, dp):
212
136
            ls = os.listdir(dp)
240
164
                                            "now of kind %r"
241
165
                                            % (fap, f_ie.kind, f_ie.file_id, fk))
242
166
 
243
 
                # make a last minute entry
244
 
                if f_ie:
245
 
                    entry = f_ie
246
 
                else:
247
 
                    if fk == 'directory':
248
 
                        entry = TreeDirectory()
249
 
                    elif fk == 'file':
250
 
                        entry = TreeFile()
251
 
                    elif fk == 'symlink':
252
 
                        entry = TreeLink()
253
 
                    else:
254
 
                        entry = TreeEntry()
255
 
                
256
 
                yield fp, c, fk, (f_ie and f_ie.file_id), entry
 
167
                yield fp, c, fk, (f_ie and f_ie.file_id)
257
168
 
258
169
                if fk != 'directory':
259
170
                    continue
275
186
            if not self.is_ignored(subp):
276
187
                yield subp
277
188
 
278
 
    def iter_conflicts(self):
279
 
        conflicted = set()
280
 
        for path in (s[0] for s in self.list_files()):
281
 
            stem = get_conflicted_stem(path)
282
 
            if stem is None:
283
 
                continue
284
 
            if stem not in conflicted:
285
 
                conflicted.add(stem)
286
 
                yield stem
287
189
 
288
190
    def extras(self):
289
191
        """Yield all unknown files in this WorkingTree.
295
197
        Currently returned depth-first, sorted by name within directories.
296
198
        """
297
199
        ## TODO: Work from given directory downwards
 
200
        from osutils import isdir, appendpath
 
201
        
298
202
        for path, dir_entry in self.inventory.directories():
299
203
            mutter("search for unknowns in %r" % path)
300
204
            dirabs = self.abspath(path)
357
261
        # Eventually it should be replaced with something more
358
262
        # accurate.
359
263
        
 
264
        import fnmatch
 
265
        from osutils import splitpath
 
266
        
360
267
        for pat in self.get_ignore_list():
361
268
            if '/' in pat or '\\' in pat:
362
269
                
375
282
                    return pat
376
283
        else:
377
284
            return None
378
 
 
379
 
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
380
 
def get_conflicted_stem(path):
381
 
    for suffix in CONFLICT_SUFFIXES:
382
 
        if path.endswith(suffix):
383
 
            return path[:-len(suffix)]
 
285
        
 
286
 
 
287
        
 
288
        
 
289