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