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