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