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