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