##// END OF EJS Templates
walk: simplify logic for badfn clause...
Matt Mackall -
r8677:34df078b default
parent child Browse files
Show More
@@ -1,619 +1,613
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from node import nullid
9 9 from i18n import _
10 10 import util, ignore, osutil, parsers
11 11 import struct, os, stat, errno
12 12 import cStringIO, sys
13 13
14 14 _unknown = ('?', 0, 0, 0)
15 15 _format = ">cllll"
16 16 propertycache = util.propertycache
17 17
18 18 def _finddirs(path):
19 19 pos = path.rfind('/')
20 20 while pos != -1:
21 21 yield path[:pos]
22 22 pos = path.rfind('/', 0, pos)
23 23
24 24 def _incdirs(dirs, path):
25 25 for base in _finddirs(path):
26 26 if base in dirs:
27 27 dirs[base] += 1
28 28 return
29 29 dirs[base] = 1
30 30
31 31 def _decdirs(dirs, path):
32 32 for base in _finddirs(path):
33 33 if dirs[base] > 1:
34 34 dirs[base] -= 1
35 35 return
36 36 del dirs[base]
37 37
38 38 class dirstate(object):
39 39
40 40 def __init__(self, opener, ui, root):
41 41 self._opener = opener
42 42 self._root = root
43 43 self._rootdir = os.path.join(root, '')
44 44 self._dirty = False
45 45 self._dirtypl = False
46 46 self._ui = ui
47 47
48 48 @propertycache
49 49 def _map(self):
50 50 self._read()
51 51 return self._map
52 52
53 53 @propertycache
54 54 def _copymap(self):
55 55 self._read()
56 56 return self._copymap
57 57
58 58 @propertycache
59 59 def _foldmap(self):
60 60 f = {}
61 61 for name in self._map:
62 62 f[os.path.normcase(name)] = name
63 63 return f
64 64
65 65 @propertycache
66 66 def _branch(self):
67 67 try:
68 68 return self._opener("branch").read().strip() or "default"
69 69 except IOError:
70 70 return "default"
71 71
72 72 @propertycache
73 73 def _pl(self):
74 74 try:
75 75 st = self._opener("dirstate").read(40)
76 76 if len(st) == 40:
77 77 return st[:20], st[20:40]
78 78 if len(st) < 40:
79 79 raise util.Abort(_('working directory state appears damaged!'))
80 80 except IOError, err:
81 81 if err.errno != errno.ENOENT: raise
82 82 return [nullid, nullid]
83 83
84 84 @propertycache
85 85 def _dirs(self):
86 86 dirs = {}
87 87 for f,s in self._map.iteritems():
88 88 if s[0] != 'r':
89 89 _incdirs(dirs, f)
90 90 return dirs
91 91
92 92 @propertycache
93 93 def _ignore(self):
94 94 files = [self._join('.hgignore')]
95 95 for name, path in self._ui.configitems("ui"):
96 96 if name == 'ignore' or name.startswith('ignore.'):
97 97 files.append(os.path.expanduser(path))
98 98 return ignore.ignore(self._root, files, self._ui.warn)
99 99
100 100 @propertycache
101 101 def _slash(self):
102 102 return self._ui.configbool('ui', 'slash') and os.sep != '/'
103 103
104 104 @propertycache
105 105 def _checklink(self):
106 106 return util.checklink(self._root)
107 107
108 108 @propertycache
109 109 def _checkexec(self):
110 110 return util.checkexec(self._root)
111 111
112 112 @propertycache
113 113 def _checkcase(self):
114 114 return not util.checkcase(self._join('.hg'))
115 115
116 116 @propertycache
117 117 def normalize(self):
118 118 if self._checkcase:
119 119 return self._normalize
120 120 return lambda x, y=False: x
121 121
122 122 def _join(self, f):
123 123 # much faster than os.path.join()
124 124 # it's safe because f is always a relative path
125 125 return self._rootdir + f
126 126
127 127 def flagfunc(self, fallback):
128 128 if self._checklink:
129 129 if self._checkexec:
130 130 def f(x):
131 131 p = self._join(x)
132 132 if os.path.islink(p):
133 133 return 'l'
134 134 if util.is_exec(p):
135 135 return 'x'
136 136 return ''
137 137 return f
138 138 def f(x):
139 139 if os.path.islink(self._join(x)):
140 140 return 'l'
141 141 if 'x' in fallback(x):
142 142 return 'x'
143 143 return ''
144 144 return f
145 145 if self._checkexec:
146 146 def f(x):
147 147 if 'l' in fallback(x):
148 148 return 'l'
149 149 if util.is_exec(self._join(x)):
150 150 return 'x'
151 151 return ''
152 152 return f
153 153 return fallback
154 154
155 155 def getcwd(self):
156 156 cwd = os.getcwd()
157 157 if cwd == self._root: return ''
158 158 # self._root ends with a path separator if self._root is '/' or 'C:\'
159 159 rootsep = self._root
160 160 if not util.endswithsep(rootsep):
161 161 rootsep += os.sep
162 162 if cwd.startswith(rootsep):
163 163 return cwd[len(rootsep):]
164 164 else:
165 165 # we're outside the repo. return an absolute path.
166 166 return cwd
167 167
168 168 def pathto(self, f, cwd=None):
169 169 if cwd is None:
170 170 cwd = self.getcwd()
171 171 path = util.pathto(self._root, cwd, f)
172 172 if self._slash:
173 173 return util.normpath(path)
174 174 return path
175 175
176 176 def __getitem__(self, key):
177 177 ''' current states:
178 178 n normal
179 179 m needs merging
180 180 r marked for removal
181 181 a marked for addition
182 182 ? not tracked'''
183 183 return self._map.get(key, ("?",))[0]
184 184
185 185 def __contains__(self, key):
186 186 return key in self._map
187 187
188 188 def __iter__(self):
189 189 for x in sorted(self._map):
190 190 yield x
191 191
192 192 def parents(self):
193 193 return self._pl
194 194
195 195 def branch(self):
196 196 return self._branch
197 197
198 198 def setparents(self, p1, p2=nullid):
199 199 self._dirty = self._dirtypl = True
200 200 self._pl = p1, p2
201 201
202 202 def setbranch(self, branch):
203 203 self._branch = branch
204 204 self._opener("branch", "w").write(branch + '\n')
205 205
206 206 def _read(self):
207 207 self._map = {}
208 208 self._copymap = {}
209 209 try:
210 210 st = self._opener("dirstate").read()
211 211 except IOError, err:
212 212 if err.errno != errno.ENOENT: raise
213 213 return
214 214 if not st:
215 215 return
216 216
217 217 p = parsers.parse_dirstate(self._map, self._copymap, st)
218 218 if not self._dirtypl:
219 219 self._pl = p
220 220
221 221 def invalidate(self):
222 222 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
223 223 if a in self.__dict__:
224 224 delattr(self, a)
225 225 self._dirty = False
226 226
227 227 def copy(self, source, dest):
228 228 """Mark dest as a copy of source. Unmark dest if source is None.
229 229 """
230 230 if source == dest:
231 231 return
232 232 self._dirty = True
233 233 if source is not None:
234 234 self._copymap[dest] = source
235 235 elif dest in self._copymap:
236 236 del self._copymap[dest]
237 237
238 238 def copied(self, file):
239 239 return self._copymap.get(file, None)
240 240
241 241 def copies(self):
242 242 return self._copymap
243 243
244 244 def _droppath(self, f):
245 245 if self[f] not in "?r" and "_dirs" in self.__dict__:
246 246 _decdirs(self._dirs, f)
247 247
248 248 def _addpath(self, f, check=False):
249 249 oldstate = self[f]
250 250 if check or oldstate == "r":
251 251 if '\r' in f or '\n' in f:
252 252 raise util.Abort(
253 253 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
254 254 if f in self._dirs:
255 255 raise util.Abort(_('directory %r already in dirstate') % f)
256 256 # shadows
257 257 for d in _finddirs(f):
258 258 if d in self._dirs:
259 259 break
260 260 if d in self._map and self[d] != 'r':
261 261 raise util.Abort(
262 262 _('file %r in dirstate clashes with %r') % (d, f))
263 263 if oldstate in "?r" and "_dirs" in self.__dict__:
264 264 _incdirs(self._dirs, f)
265 265
266 266 def normal(self, f):
267 267 'mark a file normal and clean'
268 268 self._dirty = True
269 269 self._addpath(f)
270 270 s = os.lstat(self._join(f))
271 271 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
272 272 if f in self._copymap:
273 273 del self._copymap[f]
274 274
275 275 def normallookup(self, f):
276 276 'mark a file normal, but possibly dirty'
277 277 if self._pl[1] != nullid and f in self._map:
278 278 # if there is a merge going on and the file was either
279 279 # in state 'm' or dirty before being removed, restore that state.
280 280 entry = self._map[f]
281 281 if entry[0] == 'r' and entry[2] in (-1, -2):
282 282 source = self._copymap.get(f)
283 283 if entry[2] == -1:
284 284 self.merge(f)
285 285 elif entry[2] == -2:
286 286 self.normaldirty(f)
287 287 if source:
288 288 self.copy(source, f)
289 289 return
290 290 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
291 291 return
292 292 self._dirty = True
293 293 self._addpath(f)
294 294 self._map[f] = ('n', 0, -1, -1)
295 295 if f in self._copymap:
296 296 del self._copymap[f]
297 297
298 298 def normaldirty(self, f):
299 299 'mark a file normal, but dirty'
300 300 self._dirty = True
301 301 self._addpath(f)
302 302 self._map[f] = ('n', 0, -2, -1)
303 303 if f in self._copymap:
304 304 del self._copymap[f]
305 305
306 306 def add(self, f):
307 307 'mark a file added'
308 308 self._dirty = True
309 309 self._addpath(f, True)
310 310 self._map[f] = ('a', 0, -1, -1)
311 311 if f in self._copymap:
312 312 del self._copymap[f]
313 313
314 314 def remove(self, f):
315 315 'mark a file removed'
316 316 self._dirty = True
317 317 self._droppath(f)
318 318 size = 0
319 319 if self._pl[1] != nullid and f in self._map:
320 320 entry = self._map[f]
321 321 if entry[0] == 'm':
322 322 size = -1
323 323 elif entry[0] == 'n' and entry[2] == -2:
324 324 size = -2
325 325 self._map[f] = ('r', 0, size, 0)
326 326 if size == 0 and f in self._copymap:
327 327 del self._copymap[f]
328 328
329 329 def merge(self, f):
330 330 'mark a file merged'
331 331 self._dirty = True
332 332 s = os.lstat(self._join(f))
333 333 self._addpath(f)
334 334 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
335 335 if f in self._copymap:
336 336 del self._copymap[f]
337 337
338 338 def forget(self, f):
339 339 'forget a file'
340 340 self._dirty = True
341 341 try:
342 342 self._droppath(f)
343 343 del self._map[f]
344 344 except KeyError:
345 345 self._ui.warn(_("not in dirstate: %s\n") % f)
346 346
347 347 def _normalize(self, path, knownpath=False):
348 348 norm_path = os.path.normcase(path)
349 349 fold_path = self._foldmap.get(norm_path, None)
350 350 if fold_path is None:
351 351 if knownpath or not os.path.exists(os.path.join(self._root, path)):
352 352 fold_path = path
353 353 else:
354 354 fold_path = self._foldmap.setdefault(norm_path,
355 355 util.fspath(path, self._root))
356 356 return fold_path
357 357
358 358 def clear(self):
359 359 self._map = {}
360 360 if "_dirs" in self.__dict__:
361 361 delattr(self, "_dirs");
362 362 self._copymap = {}
363 363 self._pl = [nullid, nullid]
364 364 self._dirty = True
365 365
366 366 def rebuild(self, parent, files):
367 367 self.clear()
368 368 for f in files:
369 369 if 'x' in files.flags(f):
370 370 self._map[f] = ('n', 0777, -1, 0)
371 371 else:
372 372 self._map[f] = ('n', 0666, -1, 0)
373 373 self._pl = (parent, nullid)
374 374 self._dirty = True
375 375
376 376 def write(self):
377 377 if not self._dirty:
378 378 return
379 379 st = self._opener("dirstate", "w", atomictemp=True)
380 380
381 381 try:
382 382 gran = int(self._ui.config('dirstate', 'granularity', 1))
383 383 except ValueError:
384 384 gran = 1
385 385 limit = sys.maxint
386 386 if gran > 0:
387 387 limit = util.fstat(st).st_mtime - gran
388 388
389 389 cs = cStringIO.StringIO()
390 390 copymap = self._copymap
391 391 pack = struct.pack
392 392 write = cs.write
393 393 write("".join(self._pl))
394 394 for f, e in self._map.iteritems():
395 395 if f in copymap:
396 396 f = "%s\0%s" % (f, copymap[f])
397 397 if e[3] > limit and e[0] == 'n':
398 398 e = (e[0], 0, -1, -1)
399 399 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
400 400 write(e)
401 401 write(f)
402 402 st.write(cs.getvalue())
403 403 st.rename()
404 404 self._dirty = self._dirtypl = False
405 405
406 406 def _dirignore(self, f):
407 407 if f == '.':
408 408 return False
409 409 if self._ignore(f):
410 410 return True
411 411 for p in _finddirs(f):
412 412 if self._ignore(p):
413 413 return True
414 414 return False
415 415
416 416 def walk(self, match, unknown, ignored):
417 417 '''
418 418 walk recursively through the directory tree, finding all files
419 419 matched by the match function
420 420
421 421 results are yielded in a tuple (filename, stat), where stat
422 422 and st is the stat result if the file was found in the directory.
423 423 '''
424 424
425 425 def fwarn(f, msg):
426 426 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
427 427 return False
428 428
429 429 def badtype(f, mode):
430 430 kind = _('unknown')
431 431 if stat.S_ISCHR(mode): kind = _('character device')
432 432 elif stat.S_ISBLK(mode): kind = _('block device')
433 433 elif stat.S_ISFIFO(mode): kind = _('fifo')
434 434 elif stat.S_ISSOCK(mode): kind = _('socket')
435 435 elif stat.S_ISDIR(mode): kind = _('directory')
436 436 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
437 437 % (self.pathto(f), kind))
438 438
439 439 ignore = self._ignore
440 440 dirignore = self._dirignore
441 441 if ignored:
442 442 ignore = util.never
443 443 dirignore = util.never
444 444 elif not unknown:
445 445 # if unknown and ignored are False, skip step 2
446 446 ignore = util.always
447 447 dirignore = util.always
448 448
449 449 matchfn = match.matchfn
450 450 badfn = match.bad
451 451 dmap = self._map
452 452 normpath = util.normpath
453 453 normalize = self.normalize
454 454 listdir = osutil.listdir
455 455 lstat = os.lstat
456 456 getkind = stat.S_IFMT
457 457 dirkind = stat.S_IFDIR
458 458 regkind = stat.S_IFREG
459 459 lnkkind = stat.S_IFLNK
460 460 join = self._join
461 461 work = []
462 462 wadd = work.append
463 463
464 464 if match.anypats():
465 465 #match.match with patterns
466 466 dostep3 = True
467 467 nomatches = False
468 468 elif not match.files():
469 469 #match.always or match.never
470 470 dostep3 = matchfn('')
471 471 nomatches = not dostep3
472 472 else:
473 473 #match.exact or match.match without pattern
474 474 dostep3 = False
475 475 nomatches = matchfn == match.exact
476 476
477 477 if nomatches:
478 478 #skip step 2
479 479 dirignore = util.always
480 480
481 481 files = set(match.files())
482 482 if not files or '.' in files:
483 483 files = ['']
484 484 results = {'.hg': None}
485 485
486 486 # step 1: find all explicit files
487 487 for ff in sorted(files):
488 488 nf = normalize(normpath(ff))
489 489 if nf in results:
490 490 continue
491 491
492 492 try:
493 493 st = lstat(join(nf))
494 494 kind = getkind(st.st_mode)
495 495 if kind == dirkind:
496 496 dostep3 = True
497 497 if nf in dmap:
498 498 #file deleted on disk but still in dirstate
499 499 results[nf] = None
500 500 if not dirignore(nf):
501 501 wadd(nf)
502 502 elif kind == regkind or kind == lnkkind:
503 503 results[nf] = st
504 504 else:
505 505 badtype(ff, kind)
506 506 if nf in dmap:
507 507 results[nf] = None
508 508 except OSError, inst:
509 keep = False
510 509 if nf in dmap: # does it exactly match a file?
511 510 results[nf] = None
512 keep = True
513 511 else: # does it match a directory?
514 512 prefix = nf + "/"
515 513 for fn in dmap:
516 514 if fn.startswith(prefix):
517 515 dostep3 = True
518 keep = True
519 516 break
520 if not keep:
521 if inst.errno != errno.ENOENT:
522 fwarn(ff, inst.strerror)
523 elif badfn(ff, inst.strerror):
524 if nf not in results and not ignore(nf) and matchfn(nf):
517 else:
518 if badfn(ff, inst.strerror) and not ignore(nf):
525 519 results[nf] = None
526 520
527 521 # step 2: visit subdirectories
528 522 while work:
529 523 nd = work.pop()
530 524 if hasattr(match, 'dir'):
531 525 match.dir(nd)
532 526 skip = None
533 527 if nd == '.':
534 528 nd = ''
535 529 else:
536 530 skip = '.hg'
537 531 try:
538 532 entries = listdir(join(nd), stat=True, skip=skip)
539 533 except OSError, inst:
540 534 if inst.errno == errno.EACCES:
541 535 fwarn(nd, inst.strerror)
542 536 continue
543 537 raise
544 538 for f, kind, st in entries:
545 539 nf = normalize(nd and (nd + "/" + f) or f, True)
546 540 if nf not in results:
547 541 if kind == dirkind:
548 542 if not ignore(nf):
549 543 wadd(nf)
550 544 if nf in dmap and matchfn(nf):
551 545 results[nf] = None
552 546 elif kind == regkind or kind == lnkkind:
553 547 if nf in dmap:
554 548 if matchfn(nf):
555 549 results[nf] = st
556 550 elif matchfn(nf) and not ignore(nf):
557 551 results[nf] = st
558 552 elif nf in dmap and matchfn(nf):
559 553 results[nf] = None
560 554
561 555 # step 3: report unseen items in the dmap hash
562 556 if dostep3 and not nomatches:
563 557 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
564 558 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
565 559 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
566 560 st = None
567 561 results[nf] = st
568 562
569 563 del results['.hg']
570 564 return results
571 565
572 566 def status(self, match, ignored, clean, unknown):
573 567 listignored, listclean, listunknown = ignored, clean, unknown
574 568 lookup, modified, added, unknown, ignored = [], [], [], [], []
575 569 removed, deleted, clean = [], [], []
576 570
577 571 dmap = self._map
578 572 ladd = lookup.append
579 573 madd = modified.append
580 574 aadd = added.append
581 575 uadd = unknown.append
582 576 iadd = ignored.append
583 577 radd = removed.append
584 578 dadd = deleted.append
585 579 cadd = clean.append
586 580
587 581 for fn, st in self.walk(match, listunknown, listignored).iteritems():
588 582 if fn not in dmap:
589 583 if (listignored or match.exact(fn)) and self._dirignore(fn):
590 584 if listignored:
591 585 iadd(fn)
592 586 elif listunknown:
593 587 uadd(fn)
594 588 continue
595 589
596 590 state, mode, size, time = dmap[fn]
597 591
598 592 if not st and state in "nma":
599 593 dadd(fn)
600 594 elif state == 'n':
601 595 if (size >= 0 and
602 596 (size != st.st_size
603 597 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
604 598 or size == -2
605 599 or fn in self._copymap):
606 600 madd(fn)
607 601 elif time != int(st.st_mtime):
608 602 ladd(fn)
609 603 elif listclean:
610 604 cadd(fn)
611 605 elif state == 'm':
612 606 madd(fn)
613 607 elif state == 'a':
614 608 aadd(fn)
615 609 elif state == 'r':
616 610 radd(fn)
617 611
618 612 return (lookup, modified, added, removed, deleted, unknown, ignored,
619 613 clean)
General Comments 0
You need to be logged in to leave comments. Login now