##// END OF EJS Templates
walk: we always have a badfn
Matt Mackall -
r8676:acd69fc2 default
parent child Browse files
Show More
@@ -1,621 +1,619 b''
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 badfn = fwarn
429 if hasattr(match, 'bad'):
430 badfn = match.bad
431 428
432 429 def badtype(f, mode):
433 430 kind = _('unknown')
434 431 if stat.S_ISCHR(mode): kind = _('character device')
435 432 elif stat.S_ISBLK(mode): kind = _('block device')
436 433 elif stat.S_ISFIFO(mode): kind = _('fifo')
437 434 elif stat.S_ISSOCK(mode): kind = _('socket')
438 435 elif stat.S_ISDIR(mode): kind = _('directory')
439 436 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
440 437 % (self.pathto(f), kind))
441 438
442 439 ignore = self._ignore
443 440 dirignore = self._dirignore
444 441 if ignored:
445 442 ignore = util.never
446 443 dirignore = util.never
447 444 elif not unknown:
448 445 # if unknown and ignored are False, skip step 2
449 446 ignore = util.always
450 447 dirignore = util.always
451 448
452 449 matchfn = match.matchfn
450 badfn = match.bad
453 451 dmap = self._map
454 452 normpath = util.normpath
455 453 normalize = self.normalize
456 454 listdir = osutil.listdir
457 455 lstat = os.lstat
458 456 getkind = stat.S_IFMT
459 457 dirkind = stat.S_IFDIR
460 458 regkind = stat.S_IFREG
461 459 lnkkind = stat.S_IFLNK
462 460 join = self._join
463 461 work = []
464 462 wadd = work.append
465 463
466 464 if match.anypats():
467 465 #match.match with patterns
468 466 dostep3 = True
469 467 nomatches = False
470 468 elif not match.files():
471 469 #match.always or match.never
472 470 dostep3 = matchfn('')
473 471 nomatches = not dostep3
474 472 else:
475 473 #match.exact or match.match without pattern
476 474 dostep3 = False
477 475 nomatches = matchfn == match.exact
478 476
479 477 if nomatches:
480 478 #skip step 2
481 479 dirignore = util.always
482 480
483 481 files = set(match.files())
484 482 if not files or '.' in files:
485 483 files = ['']
486 484 results = {'.hg': None}
487 485
488 486 # step 1: find all explicit files
489 487 for ff in sorted(files):
490 488 nf = normalize(normpath(ff))
491 489 if nf in results:
492 490 continue
493 491
494 492 try:
495 493 st = lstat(join(nf))
496 494 kind = getkind(st.st_mode)
497 495 if kind == dirkind:
498 496 dostep3 = True
499 497 if nf in dmap:
500 498 #file deleted on disk but still in dirstate
501 499 results[nf] = None
502 500 if not dirignore(nf):
503 501 wadd(nf)
504 502 elif kind == regkind or kind == lnkkind:
505 503 results[nf] = st
506 504 else:
507 505 badtype(ff, kind)
508 506 if nf in dmap:
509 507 results[nf] = None
510 508 except OSError, inst:
511 509 keep = False
512 510 if nf in dmap: # does it exactly match a file?
513 511 results[nf] = None
514 512 keep = True
515 513 else: # does it match a directory?
516 514 prefix = nf + "/"
517 515 for fn in dmap:
518 516 if fn.startswith(prefix):
519 517 dostep3 = True
520 518 keep = True
521 519 break
522 520 if not keep:
523 521 if inst.errno != errno.ENOENT:
524 522 fwarn(ff, inst.strerror)
525 523 elif badfn(ff, inst.strerror):
526 524 if nf not in results and not ignore(nf) and matchfn(nf):
527 525 results[nf] = None
528 526
529 527 # step 2: visit subdirectories
530 528 while work:
531 529 nd = work.pop()
532 530 if hasattr(match, 'dir'):
533 531 match.dir(nd)
534 532 skip = None
535 533 if nd == '.':
536 534 nd = ''
537 535 else:
538 536 skip = '.hg'
539 537 try:
540 538 entries = listdir(join(nd), stat=True, skip=skip)
541 539 except OSError, inst:
542 540 if inst.errno == errno.EACCES:
543 541 fwarn(nd, inst.strerror)
544 542 continue
545 543 raise
546 544 for f, kind, st in entries:
547 545 nf = normalize(nd and (nd + "/" + f) or f, True)
548 546 if nf not in results:
549 547 if kind == dirkind:
550 548 if not ignore(nf):
551 549 wadd(nf)
552 550 if nf in dmap and matchfn(nf):
553 551 results[nf] = None
554 552 elif kind == regkind or kind == lnkkind:
555 553 if nf in dmap:
556 554 if matchfn(nf):
557 555 results[nf] = st
558 556 elif matchfn(nf) and not ignore(nf):
559 557 results[nf] = st
560 558 elif nf in dmap and matchfn(nf):
561 559 results[nf] = None
562 560
563 561 # step 3: report unseen items in the dmap hash
564 562 if dostep3 and not nomatches:
565 563 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
566 564 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
567 565 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
568 566 st = None
569 567 results[nf] = st
570 568
571 569 del results['.hg']
572 570 return results
573 571
574 572 def status(self, match, ignored, clean, unknown):
575 573 listignored, listclean, listunknown = ignored, clean, unknown
576 574 lookup, modified, added, unknown, ignored = [], [], [], [], []
577 575 removed, deleted, clean = [], [], []
578 576
579 577 dmap = self._map
580 578 ladd = lookup.append
581 579 madd = modified.append
582 580 aadd = added.append
583 581 uadd = unknown.append
584 582 iadd = ignored.append
585 583 radd = removed.append
586 584 dadd = deleted.append
587 585 cadd = clean.append
588 586
589 587 for fn, st in self.walk(match, listunknown, listignored).iteritems():
590 588 if fn not in dmap:
591 589 if (listignored or match.exact(fn)) and self._dirignore(fn):
592 590 if listignored:
593 591 iadd(fn)
594 592 elif listunknown:
595 593 uadd(fn)
596 594 continue
597 595
598 596 state, mode, size, time = dmap[fn]
599 597
600 598 if not st and state in "nma":
601 599 dadd(fn)
602 600 elif state == 'n':
603 601 if (size >= 0 and
604 602 (size != st.st_size
605 603 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
606 604 or size == -2
607 605 or fn in self._copymap):
608 606 madd(fn)
609 607 elif time != int(st.st_mtime):
610 608 ladd(fn)
611 609 elif listclean:
612 610 cadd(fn)
613 611 elif state == 'm':
614 612 madd(fn)
615 613 elif state == 'a':
616 614 aadd(fn)
617 615 elif state == 'r':
618 616 radd(fn)
619 617
620 618 return (lookup, modified, added, removed, deleted, unknown, ignored,
621 619 clean)
General Comments 0
You need to be logged in to leave comments. Login now