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