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