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