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