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