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