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