##// END OF EJS Templates
dirstate: fix typo introduced by 3507f6c7715c
Patrick Mezard -
r8804:820723a4 default
parent child Browse files
Show More
@@ -1,601 +1,601
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 376 try:
377 377 gran = int(self._ui.config('dirstate', 'granularity', 1))
378 378 except ValueError:
379 379 gran = 1
380 380 limit = sys.maxint
381 381 if gran > 0:
382 382 limit = util.fstat(st).st_mtime - gran
383 383
384 384 cs = cStringIO.StringIO()
385 385 copymap = self._copymap
386 386 pack = struct.pack
387 387 write = cs.write
388 388 write("".join(self._pl))
389 389 for f, e in self._map.iteritems():
390 390 if f in copymap:
391 391 f = "%s\0%s" % (f, copymap[f])
392 392 if e[3] > limit and e[0] == 'n':
393 393 e = (e[0], 0, -1, -1)
394 394 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
395 395 write(e)
396 396 write(f)
397 397 st.write(cs.getvalue())
398 398 st.rename()
399 399 self._dirty = self._dirtypl = False
400 400
401 401 def _dirignore(self, f):
402 402 if f == '.':
403 403 return False
404 404 if self._ignore(f):
405 405 return True
406 406 for p in _finddirs(f):
407 407 if self._ignore(p):
408 408 return True
409 409 return False
410 410
411 411 def walk(self, match, unknown, ignored):
412 412 '''
413 413 walk recursively through the directory tree, finding all files
414 414 matched by the match function
415 415
416 416 results are yielded in a tuple (filename, stat), where stat
417 417 and st is the stat result if the file was found in the directory.
418 418 '''
419 419
420 420 def fwarn(f, msg):
421 421 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
422 422 return False
423 423
424 424 def badtype(mode):
425 425 kind = _('unknown')
426 426 if stat.S_ISCHR(mode): kind = _('character device')
427 427 elif stat.S_ISBLK(mode): kind = _('block device')
428 428 elif stat.S_ISFIFO(mode): kind = _('fifo')
429 429 elif stat.S_ISSOCK(mode): kind = _('socket')
430 430 elif stat.S_ISDIR(mode): kind = _('directory')
431 431 return _('unsupported file type (type is %s)') % kind
432 432
433 433 ignore = self._ignore
434 434 dirignore = self._dirignore
435 435 if ignored:
436 436 ignore = util.never
437 437 dirignore = util.never
438 438 elif not unknown:
439 439 # if unknown and ignored are False, skip step 2
440 440 ignore = util.always
441 441 dirignore = util.always
442 442
443 443 matchfn = match.matchfn
444 444 badfn = match.bad
445 445 dmap = self._map
446 446 normpath = util.normpath
447 447 listdir = osutil.listdir
448 448 lstat = os.lstat
449 449 getkind = stat.S_IFMT
450 450 dirkind = stat.S_IFDIR
451 451 regkind = stat.S_IFREG
452 452 lnkkind = stat.S_IFLNK
453 453 join = self._join
454 454 work = []
455 455 wadd = work.append
456 456
457 457 if self._checkcase:
458 458 normalize = self._normalize
459 459 else:
460 460 normalize = lambda x, y: x
461 461
462 462 exact = skipstep3 = False
463 463 if matchfn == match.exact: # match.exact
464 464 exact = True
465 465 dirignore = util.always # skip step 2
466 466 elif match.files() and not match.anypats(): # match.match, no patterns
467 467 skipstep3 = True
468 468
469 469 files = set(match.files())
470 470 if not files or '.' in files:
471 471 files = ['']
472 472 results = {'.hg': None}
473 473
474 474 # step 1: find all explicit files
475 475 for ff in sorted(files):
476 nf = normalize(normpath(ff), True)
476 nf = normalize(normpath(ff), False)
477 477 if nf in results:
478 478 continue
479 479
480 480 try:
481 481 st = lstat(join(nf))
482 482 kind = getkind(st.st_mode)
483 483 if kind == dirkind:
484 484 skipstep3 = False
485 485 if nf in dmap:
486 486 #file deleted on disk but still in dirstate
487 487 results[nf] = None
488 488 match.dir(nf)
489 489 if not dirignore(nf):
490 490 wadd(nf)
491 491 elif kind == regkind or kind == lnkkind:
492 492 results[nf] = st
493 493 else:
494 494 badfn(ff, badtype(kind))
495 495 if nf in dmap:
496 496 results[nf] = None
497 497 except OSError, inst:
498 498 if nf in dmap: # does it exactly match a file?
499 499 results[nf] = None
500 500 else: # does it match a directory?
501 501 prefix = nf + "/"
502 502 for fn in dmap:
503 503 if fn.startswith(prefix):
504 504 match.dir(nf)
505 505 skipstep3 = False
506 506 break
507 507 else:
508 508 badfn(ff, inst.strerror)
509 509
510 510 # step 2: visit subdirectories
511 511 while work:
512 512 nd = work.pop()
513 513 skip = None
514 514 if nd == '.':
515 515 nd = ''
516 516 else:
517 517 skip = '.hg'
518 518 try:
519 519 entries = listdir(join(nd), stat=True, skip=skip)
520 520 except OSError, inst:
521 521 if inst.errno == errno.EACCES:
522 522 fwarn(nd, inst.strerror)
523 523 continue
524 524 raise
525 525 for f, kind, st in entries:
526 526 nf = normalize(nd and (nd + "/" + f) or f, True)
527 527 if nf not in results:
528 528 if kind == dirkind:
529 529 if not ignore(nf):
530 530 match.dir(nf)
531 531 wadd(nf)
532 532 if nf in dmap and matchfn(nf):
533 533 results[nf] = None
534 534 elif kind == regkind or kind == lnkkind:
535 535 if nf in dmap:
536 536 if matchfn(nf):
537 537 results[nf] = st
538 538 elif matchfn(nf) and not ignore(nf):
539 539 results[nf] = st
540 540 elif nf in dmap and matchfn(nf):
541 541 results[nf] = None
542 542
543 543 # step 3: report unseen items in the dmap hash
544 544 if not skipstep3 and not exact:
545 545 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
546 546 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
547 547 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
548 548 st = None
549 549 results[nf] = st
550 550
551 551 del results['.hg']
552 552 return results
553 553
554 554 def status(self, match, ignored, clean, unknown):
555 555 listignored, listclean, listunknown = ignored, clean, unknown
556 556 lookup, modified, added, unknown, ignored = [], [], [], [], []
557 557 removed, deleted, clean = [], [], []
558 558
559 559 dmap = self._map
560 560 ladd = lookup.append
561 561 madd = modified.append
562 562 aadd = added.append
563 563 uadd = unknown.append
564 564 iadd = ignored.append
565 565 radd = removed.append
566 566 dadd = deleted.append
567 567 cadd = clean.append
568 568
569 569 for fn, st in self.walk(match, listunknown, listignored).iteritems():
570 570 if fn not in dmap:
571 571 if (listignored or match.exact(fn)) and self._dirignore(fn):
572 572 if listignored:
573 573 iadd(fn)
574 574 elif listunknown:
575 575 uadd(fn)
576 576 continue
577 577
578 578 state, mode, size, time = dmap[fn]
579 579
580 580 if not st and state in "nma":
581 581 dadd(fn)
582 582 elif state == 'n':
583 583 if (size >= 0 and
584 584 (size != st.st_size
585 585 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
586 586 or size == -2
587 587 or fn in self._copymap):
588 588 madd(fn)
589 589 elif time != int(st.st_mtime):
590 590 ladd(fn)
591 591 elif listclean:
592 592 cadd(fn)
593 593 elif state == 'm':
594 594 madd(fn)
595 595 elif state == 'a':
596 596 aadd(fn)
597 597 elif state == 'r':
598 598 radd(fn)
599 599
600 600 return (lookup, modified, added, removed, deleted, unknown, ignored,
601 601 clean)
General Comments 0
You need to be logged in to leave comments. Login now