##// END OF EJS Templates
dirstate: use getattr rather than lazyread
Matt Mackall -
r4603:0f6853c1 default
parent child Browse files
Show More
@@ -1,602 +1,593 b''
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8 """
8 """
9
9
10 from node import *
10 from node import *
11 from i18n import _
11 from i18n import _
12 import struct, os, time, bisect, stat, strutil, util, re, errno
12 import struct, os, time, bisect, stat, strutil, util, re, errno
13 import cStringIO
13 import cStringIO
14
14
15 class dirstate(object):
15 class dirstate(object):
16 format = ">cllll"
16 format = ">cllll"
17
17
18 def __init__(self, opener, ui, root):
18 def __init__(self, opener, ui, root):
19 self.opener = opener
19 self.opener = opener
20 self.root = root
20 self.root = root
21 self.dirty = 0
21 self.dirty = 0
22 self.ui = ui
22 self.ui = ui
23 self.map = None
24 self.fp = None
23 self.fp = None
25 self.pl = None
24 self.pl = None
26 self.dirs = None
25 self.dirs = None
27 self.copymap = {}
26 self.copymap = {}
28 self.ignorefunc = None
27 self.ignorefunc = None
29 self._branch = None
28 self._branch = None
30 self._slash = None
29 self._slash = None
31
30
31 def __getattr__(self, name):
32 if name == 'map':
33 self.read()
34 return self.map
35 else:
36 raise AttributeError, name
37
32 def wjoin(self, f):
38 def wjoin(self, f):
33 return os.path.join(self.root, f)
39 return os.path.join(self.root, f)
34
40
35 def getcwd(self):
41 def getcwd(self):
36 cwd = os.getcwd()
42 cwd = os.getcwd()
37 if cwd == self.root: return ''
43 if cwd == self.root: return ''
38 # self.root ends with a path separator if self.root is '/' or 'C:\'
44 # self.root ends with a path separator if self.root is '/' or 'C:\'
39 rootsep = self.root
45 rootsep = self.root
40 if not rootsep.endswith(os.sep):
46 if not rootsep.endswith(os.sep):
41 rootsep += os.sep
47 rootsep += os.sep
42 if cwd.startswith(rootsep):
48 if cwd.startswith(rootsep):
43 return cwd[len(rootsep):]
49 return cwd[len(rootsep):]
44 else:
50 else:
45 # we're outside the repo. return an absolute path.
51 # we're outside the repo. return an absolute path.
46 return cwd
52 return cwd
47
53
48 def pathto(self, f, cwd=None):
54 def pathto(self, f, cwd=None):
49 if cwd is None:
55 if cwd is None:
50 cwd = self.getcwd()
56 cwd = self.getcwd()
51 path = util.pathto(self.root, cwd, f)
57 path = util.pathto(self.root, cwd, f)
52 if self._slash is None:
58 if self._slash is None:
53 self._slash = self.ui.configbool('ui', 'slash') and os.sep != '/'
59 self._slash = self.ui.configbool('ui', 'slash') and os.sep != '/'
54 if self._slash:
60 if self._slash:
55 path = path.replace(os.sep, '/')
61 path = path.replace(os.sep, '/')
56 return path
62 return path
57
63
58 def hgignore(self):
64 def hgignore(self):
59 '''return the contents of .hgignore files as a list of patterns.
65 '''return the contents of .hgignore files as a list of patterns.
60
66
61 the files parsed for patterns include:
67 the files parsed for patterns include:
62 .hgignore in the repository root
68 .hgignore in the repository root
63 any additional files specified in the [ui] section of ~/.hgrc
69 any additional files specified in the [ui] section of ~/.hgrc
64
70
65 trailing white space is dropped.
71 trailing white space is dropped.
66 the escape character is backslash.
72 the escape character is backslash.
67 comments start with #.
73 comments start with #.
68 empty lines are skipped.
74 empty lines are skipped.
69
75
70 lines can be of the following formats:
76 lines can be of the following formats:
71
77
72 syntax: regexp # defaults following lines to non-rooted regexps
78 syntax: regexp # defaults following lines to non-rooted regexps
73 syntax: glob # defaults following lines to non-rooted globs
79 syntax: glob # defaults following lines to non-rooted globs
74 re:pattern # non-rooted regular expression
80 re:pattern # non-rooted regular expression
75 glob:pattern # non-rooted glob
81 glob:pattern # non-rooted glob
76 pattern # pattern of the current default type'''
82 pattern # pattern of the current default type'''
77 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
83 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
78 def parselines(fp):
84 def parselines(fp):
79 for line in fp:
85 for line in fp:
80 if not line.endswith('\n'):
86 if not line.endswith('\n'):
81 line += '\n'
87 line += '\n'
82 escape = False
88 escape = False
83 for i in xrange(len(line)):
89 for i in xrange(len(line)):
84 if escape: escape = False
90 if escape: escape = False
85 elif line[i] == '\\': escape = True
91 elif line[i] == '\\': escape = True
86 elif line[i] == '#': break
92 elif line[i] == '#': break
87 line = line[:i].rstrip()
93 line = line[:i].rstrip()
88 if line: yield line
94 if line: yield line
89 repoignore = self.wjoin('.hgignore')
95 repoignore = self.wjoin('.hgignore')
90 files = [repoignore]
96 files = [repoignore]
91 files.extend(self.ui.hgignorefiles())
97 files.extend(self.ui.hgignorefiles())
92 pats = {}
98 pats = {}
93 for f in files:
99 for f in files:
94 try:
100 try:
95 pats[f] = []
101 pats[f] = []
96 fp = open(f)
102 fp = open(f)
97 syntax = 'relre:'
103 syntax = 'relre:'
98 for line in parselines(fp):
104 for line in parselines(fp):
99 if line.startswith('syntax:'):
105 if line.startswith('syntax:'):
100 s = line[7:].strip()
106 s = line[7:].strip()
101 try:
107 try:
102 syntax = syntaxes[s]
108 syntax = syntaxes[s]
103 except KeyError:
109 except KeyError:
104 self.ui.warn(_("%s: ignoring invalid "
110 self.ui.warn(_("%s: ignoring invalid "
105 "syntax '%s'\n") % (f, s))
111 "syntax '%s'\n") % (f, s))
106 continue
112 continue
107 pat = syntax + line
113 pat = syntax + line
108 for s in syntaxes.values():
114 for s in syntaxes.values():
109 if line.startswith(s):
115 if line.startswith(s):
110 pat = line
116 pat = line
111 break
117 break
112 pats[f].append(pat)
118 pats[f].append(pat)
113 except IOError, inst:
119 except IOError, inst:
114 if f != repoignore:
120 if f != repoignore:
115 self.ui.warn(_("skipping unreadable ignore file"
121 self.ui.warn(_("skipping unreadable ignore file"
116 " '%s': %s\n") % (f, inst.strerror))
122 " '%s': %s\n") % (f, inst.strerror))
117 return pats
123 return pats
118
124
119 def ignore(self, fn):
125 def ignore(self, fn):
120 '''default match function used by dirstate and
126 '''default match function used by dirstate and
121 localrepository. this honours the repository .hgignore file
127 localrepository. this honours the repository .hgignore file
122 and any other files specified in the [ui] section of .hgrc.'''
128 and any other files specified in the [ui] section of .hgrc.'''
123 if not self.ignorefunc:
129 if not self.ignorefunc:
124 ignore = self.hgignore()
130 ignore = self.hgignore()
125 allpats = []
131 allpats = []
126 [allpats.extend(patlist) for patlist in ignore.values()]
132 [allpats.extend(patlist) for patlist in ignore.values()]
127 if allpats:
133 if allpats:
128 try:
134 try:
129 files, self.ignorefunc, anypats = (
135 files, self.ignorefunc, anypats = (
130 util.matcher(self.root, inc=allpats, src='.hgignore'))
136 util.matcher(self.root, inc=allpats, src='.hgignore'))
131 except util.Abort:
137 except util.Abort:
132 # Re-raise an exception where the src is the right file
138 # Re-raise an exception where the src is the right file
133 for f, patlist in ignore.items():
139 for f, patlist in ignore.items():
134 files, self.ignorefunc, anypats = (
140 files, self.ignorefunc, anypats = (
135 util.matcher(self.root, inc=patlist, src=f))
141 util.matcher(self.root, inc=patlist, src=f))
136 else:
142 else:
137 self.ignorefunc = util.never
143 self.ignorefunc = util.never
138 return self.ignorefunc(fn)
144 return self.ignorefunc(fn)
139
145
140 def __del__(self):
146 def __del__(self):
141 if self.dirty:
147 if self.dirty:
142 self.write()
148 self.write()
143
149
144 def __getitem__(self, key):
150 def __getitem__(self, key):
145 try:
151 return self.map[key]
146 return self.map[key]
147 except TypeError:
148 self.lazyread()
149 return self[key]
150
152
151 _unknown = ('?', 0, 0, 0)
153 _unknown = ('?', 0, 0, 0)
152
154
153 def get(self, key):
155 def get(self, key):
154 try:
156 try:
155 return self[key]
157 return self[key]
156 except KeyError:
158 except KeyError:
157 return self._unknown
159 return self._unknown
158
160
159 def __contains__(self, key):
161 def __contains__(self, key):
160 self.lazyread()
161 return key in self.map
162 return key in self.map
162
163
163 def parents(self):
164 def parents(self):
164 if self.pl is None:
165 if self.pl is None:
165 self.pl = [nullid, nullid]
166 self.pl = [nullid, nullid]
166 try:
167 try:
167 self.fp = self.opener('dirstate')
168 self.fp = self.opener('dirstate')
168 st = self.fp.read(40)
169 st = self.fp.read(40)
169 if len(st) == 40:
170 if len(st) == 40:
170 self.pl = st[:20], st[20:40]
171 self.pl = st[:20], st[20:40]
171 except IOError, err:
172 except IOError, err:
172 if err.errno != errno.ENOENT: raise
173 if err.errno != errno.ENOENT: raise
173 return self.pl
174 return self.pl
174
175
175 def branch(self):
176 def branch(self):
176 if not self._branch:
177 if not self._branch:
177 try:
178 try:
178 self._branch = self.opener("branch").read().strip()\
179 self._branch = self.opener("branch").read().strip()\
179 or "default"
180 or "default"
180 except IOError:
181 except IOError:
181 self._branch = "default"
182 self._branch = "default"
182 return self._branch
183 return self._branch
183
184
184 def markdirty(self):
185 def markdirty(self):
185 if not self.dirty:
186 if not self.dirty:
186 self.dirty = 1
187 self.dirty = 1
187
188
188 def setparents(self, p1, p2=nullid):
189 def setparents(self, p1, p2=nullid):
189 self.lazyread()
190 self.markdirty()
190 self.markdirty()
191 self.pl = p1, p2
191 self.pl = p1, p2
192
192
193 def setbranch(self, branch):
193 def setbranch(self, branch):
194 self._branch = branch
194 self._branch = branch
195 self.opener("branch", "w").write(branch + '\n')
195 self.opener("branch", "w").write(branch + '\n')
196
196
197 def state(self, key):
197 def state(self, key):
198 try:
198 try:
199 return self[key][0]
199 return self[key][0]
200 except KeyError:
200 except KeyError:
201 return "?"
201 return "?"
202
202
203 def lazyread(self):
204 if self.map is None:
205 self.read()
206
207 def parse(self, st):
203 def parse(self, st):
208 self.pl = [st[:20], st[20: 40]]
204 self.pl = [st[:20], st[20: 40]]
209
205
210 # deref fields so they will be local in loop
206 # deref fields so they will be local in loop
211 map = self.map
207 map = self.map
212 copymap = self.copymap
208 copymap = self.copymap
213 format = self.format
209 format = self.format
214 unpack = struct.unpack
210 unpack = struct.unpack
215
211
216 pos = 40
212 pos = 40
217 e_size = struct.calcsize(format)
213 e_size = struct.calcsize(format)
218
214
219 while pos < len(st):
215 while pos < len(st):
220 newpos = pos + e_size
216 newpos = pos + e_size
221 e = unpack(format, st[pos:newpos])
217 e = unpack(format, st[pos:newpos])
222 l = e[4]
218 l = e[4]
223 pos = newpos
219 pos = newpos
224 newpos = pos + l
220 newpos = pos + l
225 f = st[pos:newpos]
221 f = st[pos:newpos]
226 if '\0' in f:
222 if '\0' in f:
227 f, c = f.split('\0')
223 f, c = f.split('\0')
228 copymap[f] = c
224 copymap[f] = c
229 map[f] = e[:4]
225 map[f] = e[:4]
230 pos = newpos
226 pos = newpos
231
227
232 def read(self):
228 def read(self):
233 self.map = {}
229 self.map = {}
234 self.pl = [nullid, nullid]
230 self.pl = [nullid, nullid]
235 try:
231 try:
236 if self.fp:
232 if self.fp:
237 self.fp.seek(0)
233 self.fp.seek(0)
238 st = self.fp.read()
234 st = self.fp.read()
239 self.fp = None
235 self.fp = None
240 else:
236 else:
241 st = self.opener("dirstate").read()
237 st = self.opener("dirstate").read()
242 if st:
238 if st:
243 self.parse(st)
239 self.parse(st)
244 except IOError, err:
240 except IOError, err:
245 if err.errno != errno.ENOENT: raise
241 if err.errno != errno.ENOENT: raise
246
242
247 def reload(self):
243 def reload(self):
248 def mtime():
244 def mtime():
249 m = self.map and self.map.get('.hgignore')
245 m = self.map and self.map.get('.hgignore')
250 return m and m[-1]
246 return m and m[-1]
251
247
252 old_mtime = self.ignorefunc and mtime()
248 old_mtime = self.ignorefunc and mtime()
253 self.read()
249 self.read()
254 if old_mtime != mtime():
250 if old_mtime != mtime():
255 self.ignorefunc = None
251 self.ignorefunc = None
256
252
257 def copy(self, source, dest):
253 def copy(self, source, dest):
258 self.lazyread()
259 self.markdirty()
254 self.markdirty()
260 self.copymap[dest] = source
255 self.copymap[dest] = source
261
256
262 def copied(self, file):
257 def copied(self, file):
263 return self.copymap.get(file, None)
258 return self.copymap.get(file, None)
264
259
265 def copies(self):
260 def copies(self):
266 return self.copymap
261 return self.copymap
267
262
268 def initdirs(self):
263 def initdirs(self):
269 if self.dirs is None:
264 if self.dirs is None:
270 self.dirs = {}
265 self.dirs = {}
271 for f in self.map:
266 for f in self.map:
272 self.updatedirs(f, 1)
267 self.updatedirs(f, 1)
273
268
274 def updatedirs(self, path, delta):
269 def updatedirs(self, path, delta):
275 if self.dirs is not None:
270 if self.dirs is not None:
276 for c in strutil.findall(path, '/'):
271 for c in strutil.findall(path, '/'):
277 pc = path[:c]
272 pc = path[:c]
278 self.dirs.setdefault(pc, 0)
273 self.dirs.setdefault(pc, 0)
279 self.dirs[pc] += delta
274 self.dirs[pc] += delta
280
275
281 def checkinterfering(self, files):
276 def checkinterfering(self, files):
282 def prefixes(f):
277 def prefixes(f):
283 for c in strutil.rfindall(f, '/'):
278 for c in strutil.rfindall(f, '/'):
284 yield f[:c]
279 yield f[:c]
285 self.lazyread()
286 self.initdirs()
280 self.initdirs()
287 seendirs = {}
281 seendirs = {}
288 for f in files:
282 for f in files:
289 # shadows
283 # shadows
290 if self.dirs.get(f):
284 if self.dirs.get(f):
291 raise util.Abort(_('directory named %r already in dirstate') %
285 raise util.Abort(_('directory named %r already in dirstate') %
292 f)
286 f)
293 for d in prefixes(f):
287 for d in prefixes(f):
294 if d in seendirs:
288 if d in seendirs:
295 break
289 break
296 if d in self.map:
290 if d in self.map:
297 raise util.Abort(_('file named %r already in dirstate') %
291 raise util.Abort(_('file named %r already in dirstate') %
298 d)
292 d)
299 seendirs[d] = True
293 seendirs[d] = True
300 # disallowed
294 # disallowed
301 if '\r' in f or '\n' in f:
295 if '\r' in f or '\n' in f:
302 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
296 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
303
297
304 def update(self, files, state, **kw):
298 def update(self, files, state, **kw):
305 ''' current states:
299 ''' current states:
306 n normal
300 n normal
307 m needs merging
301 m needs merging
308 r marked for removal
302 r marked for removal
309 a marked for addition'''
303 a marked for addition'''
310
304
311 if not files: return
305 if not files: return
312 self.lazyread()
313 self.markdirty()
306 self.markdirty()
314 if state == "a":
307 if state == "a":
315 self.initdirs()
308 self.initdirs()
316 self.checkinterfering(files)
309 self.checkinterfering(files)
317 for f in files:
310 for f in files:
318 if state == "r":
311 if state == "r":
319 self.map[f] = ('r', 0, 0, 0)
312 self.map[f] = ('r', 0, 0, 0)
320 self.updatedirs(f, -1)
313 self.updatedirs(f, -1)
321 else:
314 else:
322 if state == "a":
315 if state == "a":
323 self.updatedirs(f, 1)
316 self.updatedirs(f, 1)
324 s = os.lstat(self.wjoin(f))
317 s = os.lstat(self.wjoin(f))
325 st_size = kw.get('st_size', s.st_size)
318 st_size = kw.get('st_size', s.st_size)
326 st_mtime = kw.get('st_mtime', s.st_mtime)
319 st_mtime = kw.get('st_mtime', s.st_mtime)
327 self.map[f] = (state, s.st_mode, st_size, st_mtime)
320 self.map[f] = (state, s.st_mode, st_size, st_mtime)
328 if self.copymap.has_key(f):
321 if self.copymap.has_key(f):
329 del self.copymap[f]
322 del self.copymap[f]
330
323
331 def forget(self, files):
324 def forget(self, files):
332 if not files: return
325 if not files: return
333 self.lazyread()
334 self.markdirty()
326 self.markdirty()
335 self.initdirs()
327 self.initdirs()
336 for f in files:
328 for f in files:
337 try:
329 try:
338 del self.map[f]
330 del self.map[f]
339 self.updatedirs(f, -1)
331 self.updatedirs(f, -1)
340 except KeyError:
332 except KeyError:
341 self.ui.warn(_("not in dirstate: %s!\n") % f)
333 self.ui.warn(_("not in dirstate: %s!\n") % f)
342 pass
334 pass
343
335
344 def clear(self):
336 def clear(self):
345 self.map = {}
337 self.map = {}
346 self.copymap = {}
338 self.copymap = {}
347 self.dirs = None
339 self.dirs = None
348 self.markdirty()
340 self.markdirty()
349
341
350 def rebuild(self, parent, files):
342 def rebuild(self, parent, files):
351 self.clear()
343 self.clear()
352 for f in files:
344 for f in files:
353 if files.execf(f):
345 if files.execf(f):
354 self.map[f] = ('n', 0777, -1, 0)
346 self.map[f] = ('n', 0777, -1, 0)
355 else:
347 else:
356 self.map[f] = ('n', 0666, -1, 0)
348 self.map[f] = ('n', 0666, -1, 0)
357 self.pl = (parent, nullid)
349 self.pl = (parent, nullid)
358 self.markdirty()
350 self.markdirty()
359
351
360 def write(self):
352 def write(self):
361 if not self.dirty:
353 if not self.dirty:
362 return
354 return
363 cs = cStringIO.StringIO()
355 cs = cStringIO.StringIO()
364 cs.write("".join(self.pl))
356 cs.write("".join(self.pl))
365 for f, e in self.map.iteritems():
357 for f, e in self.map.iteritems():
366 c = self.copied(f)
358 c = self.copied(f)
367 if c:
359 if c:
368 f = f + "\0" + c
360 f = f + "\0" + c
369 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
361 e = struct.pack(self.format, e[0], e[1], e[2], e[3], len(f))
370 cs.write(e)
362 cs.write(e)
371 cs.write(f)
363 cs.write(f)
372 st = self.opener("dirstate", "w", atomictemp=True)
364 st = self.opener("dirstate", "w", atomictemp=True)
373 st.write(cs.getvalue())
365 st.write(cs.getvalue())
374 st.rename()
366 st.rename()
375 self.dirty = 0
367 self.dirty = 0
376
368
377 def filterfiles(self, files):
369 def filterfiles(self, files):
378 ret = {}
370 ret = {}
379 unknown = []
371 unknown = []
380
372
381 for x in files:
373 for x in files:
382 if x == '.':
374 if x == '.':
383 return self.map.copy()
375 return self.map.copy()
384 if x not in self.map:
376 if x not in self.map:
385 unknown.append(x)
377 unknown.append(x)
386 else:
378 else:
387 ret[x] = self.map[x]
379 ret[x] = self.map[x]
388
380
389 if not unknown:
381 if not unknown:
390 return ret
382 return ret
391
383
392 b = self.map.keys()
384 b = self.map.keys()
393 b.sort()
385 b.sort()
394 blen = len(b)
386 blen = len(b)
395
387
396 for x in unknown:
388 for x in unknown:
397 bs = bisect.bisect(b, "%s%s" % (x, '/'))
389 bs = bisect.bisect(b, "%s%s" % (x, '/'))
398 while bs < blen:
390 while bs < blen:
399 s = b[bs]
391 s = b[bs]
400 if len(s) > len(x) and s.startswith(x):
392 if len(s) > len(x) and s.startswith(x):
401 ret[s] = self.map[s]
393 ret[s] = self.map[s]
402 else:
394 else:
403 break
395 break
404 bs += 1
396 bs += 1
405 return ret
397 return ret
406
398
407 def supported_type(self, f, st, verbose=False):
399 def supported_type(self, f, st, verbose=False):
408 if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
400 if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
409 return True
401 return True
410 if verbose:
402 if verbose:
411 kind = 'unknown'
403 kind = 'unknown'
412 if stat.S_ISCHR(st.st_mode): kind = _('character device')
404 if stat.S_ISCHR(st.st_mode): kind = _('character device')
413 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
405 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
414 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
406 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
415 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
407 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
416 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
408 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
417 self.ui.warn(_('%s: unsupported file type (type is %s)\n')
409 self.ui.warn(_('%s: unsupported file type (type is %s)\n')
418 % (self.pathto(f), kind))
410 % (self.pathto(f), kind))
419 return False
411 return False
420
412
421 def walk(self, files=None, match=util.always, badmatch=None):
413 def walk(self, files=None, match=util.always, badmatch=None):
422 # filter out the stat
414 # filter out the stat
423 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
415 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
424 yield src, f
416 yield src, f
425
417
426 def statwalk(self, files=None, match=util.always, ignored=False,
418 def statwalk(self, files=None, match=util.always, ignored=False,
427 badmatch=None, directories=False):
419 badmatch=None, directories=False):
428 '''
420 '''
429 walk recursively through the directory tree, finding all files
421 walk recursively through the directory tree, finding all files
430 matched by the match function
422 matched by the match function
431
423
432 results are yielded in a tuple (src, filename, st), where src
424 results are yielded in a tuple (src, filename, st), where src
433 is one of:
425 is one of:
434 'f' the file was found in the directory tree
426 'f' the file was found in the directory tree
435 'd' the file is a directory of the tree
427 'd' the file is a directory of the tree
436 'm' the file was only in the dirstate and not in the tree
428 'm' the file was only in the dirstate and not in the tree
437 'b' file was not found and matched badmatch
429 'b' file was not found and matched badmatch
438
430
439 and st is the stat result if the file was found in the directory.
431 and st is the stat result if the file was found in the directory.
440 '''
432 '''
441 self.lazyread()
442
433
443 # walk all files by default
434 # walk all files by default
444 if not files:
435 if not files:
445 files = ['.']
436 files = ['.']
446 dc = self.map.copy()
437 dc = self.map.copy()
447 else:
438 else:
448 files = util.unique(files)
439 files = util.unique(files)
449 dc = self.filterfiles(files)
440 dc = self.filterfiles(files)
450
441
451 def imatch(file_):
442 def imatch(file_):
452 if file_ not in dc and self.ignore(file_):
443 if file_ not in dc and self.ignore(file_):
453 return False
444 return False
454 return match(file_)
445 return match(file_)
455
446
456 ignore = self.ignore
447 ignore = self.ignore
457 if ignored:
448 if ignored:
458 imatch = match
449 imatch = match
459 ignore = util.never
450 ignore = util.never
460
451
461 # self.root may end with a path separator when self.root == '/'
452 # self.root may end with a path separator when self.root == '/'
462 common_prefix_len = len(self.root)
453 common_prefix_len = len(self.root)
463 if not self.root.endswith(os.sep):
454 if not self.root.endswith(os.sep):
464 common_prefix_len += 1
455 common_prefix_len += 1
465 # recursion free walker, faster than os.walk.
456 # recursion free walker, faster than os.walk.
466 def findfiles(s):
457 def findfiles(s):
467 work = [s]
458 work = [s]
468 if directories:
459 if directories:
469 yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
460 yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s)
470 while work:
461 while work:
471 top = work.pop()
462 top = work.pop()
472 names = os.listdir(top)
463 names = os.listdir(top)
473 names.sort()
464 names.sort()
474 # nd is the top of the repository dir tree
465 # nd is the top of the repository dir tree
475 nd = util.normpath(top[common_prefix_len:])
466 nd = util.normpath(top[common_prefix_len:])
476 if nd == '.':
467 if nd == '.':
477 nd = ''
468 nd = ''
478 else:
469 else:
479 # do not recurse into a repo contained in this
470 # do not recurse into a repo contained in this
480 # one. use bisect to find .hg directory so speed
471 # one. use bisect to find .hg directory so speed
481 # is good on big directory.
472 # is good on big directory.
482 hg = bisect.bisect_left(names, '.hg')
473 hg = bisect.bisect_left(names, '.hg')
483 if hg < len(names) and names[hg] == '.hg':
474 if hg < len(names) and names[hg] == '.hg':
484 if os.path.isdir(os.path.join(top, '.hg')):
475 if os.path.isdir(os.path.join(top, '.hg')):
485 continue
476 continue
486 for f in names:
477 for f in names:
487 np = util.pconvert(os.path.join(nd, f))
478 np = util.pconvert(os.path.join(nd, f))
488 if seen(np):
479 if seen(np):
489 continue
480 continue
490 p = os.path.join(top, f)
481 p = os.path.join(top, f)
491 # don't trip over symlinks
482 # don't trip over symlinks
492 st = os.lstat(p)
483 st = os.lstat(p)
493 if stat.S_ISDIR(st.st_mode):
484 if stat.S_ISDIR(st.st_mode):
494 if not ignore(np):
485 if not ignore(np):
495 work.append(p)
486 work.append(p)
496 if directories:
487 if directories:
497 yield 'd', np, st
488 yield 'd', np, st
498 if imatch(np) and np in dc:
489 if imatch(np) and np in dc:
499 yield 'm', np, st
490 yield 'm', np, st
500 elif imatch(np):
491 elif imatch(np):
501 if self.supported_type(np, st):
492 if self.supported_type(np, st):
502 yield 'f', np, st
493 yield 'f', np, st
503 elif np in dc:
494 elif np in dc:
504 yield 'm', np, st
495 yield 'm', np, st
505
496
506 known = {'.hg': 1}
497 known = {'.hg': 1}
507 def seen(fn):
498 def seen(fn):
508 if fn in known: return True
499 if fn in known: return True
509 known[fn] = 1
500 known[fn] = 1
510
501
511 # step one, find all files that match our criteria
502 # step one, find all files that match our criteria
512 files.sort()
503 files.sort()
513 for ff in files:
504 for ff in files:
514 nf = util.normpath(ff)
505 nf = util.normpath(ff)
515 f = self.wjoin(ff)
506 f = self.wjoin(ff)
516 try:
507 try:
517 st = os.lstat(f)
508 st = os.lstat(f)
518 except OSError, inst:
509 except OSError, inst:
519 found = False
510 found = False
520 for fn in dc:
511 for fn in dc:
521 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
512 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
522 found = True
513 found = True
523 break
514 break
524 if not found:
515 if not found:
525 if inst.errno != errno.ENOENT or not badmatch:
516 if inst.errno != errno.ENOENT or not badmatch:
526 self.ui.warn('%s: %s\n' % (self.pathto(ff),
517 self.ui.warn('%s: %s\n' % (self.pathto(ff),
527 inst.strerror))
518 inst.strerror))
528 elif badmatch and badmatch(ff) and imatch(nf):
519 elif badmatch and badmatch(ff) and imatch(nf):
529 yield 'b', ff, None
520 yield 'b', ff, None
530 continue
521 continue
531 if stat.S_ISDIR(st.st_mode):
522 if stat.S_ISDIR(st.st_mode):
532 cmp1 = (lambda x, y: cmp(x[1], y[1]))
523 cmp1 = (lambda x, y: cmp(x[1], y[1]))
533 sorted_ = [ x for x in findfiles(f) ]
524 sorted_ = [ x for x in findfiles(f) ]
534 sorted_.sort(cmp1)
525 sorted_.sort(cmp1)
535 for e in sorted_:
526 for e in sorted_:
536 yield e
527 yield e
537 else:
528 else:
538 if not seen(nf) and match(nf):
529 if not seen(nf) and match(nf):
539 if self.supported_type(ff, st, verbose=True):
530 if self.supported_type(ff, st, verbose=True):
540 yield 'f', nf, st
531 yield 'f', nf, st
541 elif ff in dc:
532 elif ff in dc:
542 yield 'm', nf, st
533 yield 'm', nf, st
543
534
544 # step two run through anything left in the dc hash and yield
535 # step two run through anything left in the dc hash and yield
545 # if we haven't already seen it
536 # if we haven't already seen it
546 ks = dc.keys()
537 ks = dc.keys()
547 ks.sort()
538 ks.sort()
548 for k in ks:
539 for k in ks:
549 if not seen(k) and imatch(k):
540 if not seen(k) and imatch(k):
550 yield 'm', k, None
541 yield 'm', k, None
551
542
552 def status(self, files=None, match=util.always, list_ignored=False,
543 def status(self, files=None, match=util.always, list_ignored=False,
553 list_clean=False):
544 list_clean=False):
554 lookup, modified, added, unknown, ignored = [], [], [], [], []
545 lookup, modified, added, unknown, ignored = [], [], [], [], []
555 removed, deleted, clean = [], [], []
546 removed, deleted, clean = [], [], []
556
547
557 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
548 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
558 try:
549 try:
559 type_, mode, size, time = self[fn]
550 type_, mode, size, time = self[fn]
560 except KeyError:
551 except KeyError:
561 if list_ignored and self.ignore(fn):
552 if list_ignored and self.ignore(fn):
562 ignored.append(fn)
553 ignored.append(fn)
563 else:
554 else:
564 unknown.append(fn)
555 unknown.append(fn)
565 continue
556 continue
566 if src == 'm':
557 if src == 'm':
567 nonexistent = True
558 nonexistent = True
568 if not st:
559 if not st:
569 try:
560 try:
570 st = os.lstat(self.wjoin(fn))
561 st = os.lstat(self.wjoin(fn))
571 except OSError, inst:
562 except OSError, inst:
572 if inst.errno != errno.ENOENT:
563 if inst.errno != errno.ENOENT:
573 raise
564 raise
574 st = None
565 st = None
575 # We need to re-check that it is a valid file
566 # We need to re-check that it is a valid file
576 if st and self.supported_type(fn, st):
567 if st and self.supported_type(fn, st):
577 nonexistent = False
568 nonexistent = False
578 # XXX: what to do with file no longer present in the fs
569 # XXX: what to do with file no longer present in the fs
579 # who are not removed in the dirstate ?
570 # who are not removed in the dirstate ?
580 if nonexistent and type_ in "nm":
571 if nonexistent and type_ in "nm":
581 deleted.append(fn)
572 deleted.append(fn)
582 continue
573 continue
583 # check the common case first
574 # check the common case first
584 if type_ == 'n':
575 if type_ == 'n':
585 if not st:
576 if not st:
586 st = os.lstat(self.wjoin(fn))
577 st = os.lstat(self.wjoin(fn))
587 if size >= 0 and (size != st.st_size
578 if size >= 0 and (size != st.st_size
588 or (mode ^ st.st_mode) & 0100):
579 or (mode ^ st.st_mode) & 0100):
589 modified.append(fn)
580 modified.append(fn)
590 elif time != int(st.st_mtime):
581 elif time != int(st.st_mtime):
591 lookup.append(fn)
582 lookup.append(fn)
592 elif list_clean:
583 elif list_clean:
593 clean.append(fn)
584 clean.append(fn)
594 elif type_ == 'm':
585 elif type_ == 'm':
595 modified.append(fn)
586 modified.append(fn)
596 elif type_ == 'a':
587 elif type_ == 'a':
597 added.append(fn)
588 added.append(fn)
598 elif type_ == 'r':
589 elif type_ == 'r':
599 removed.append(fn)
590 removed.append(fn)
600
591
601 return (lookup, modified, added, removed, deleted, unknown, ignored,
592 return (lookup, modified, added, removed, deleted, unknown, ignored,
602 clean)
593 clean)
General Comments 0
You need to be logged in to leave comments. Login now