##// END OF EJS Templates
Add a folding() method to dirstate...
Paul Moore -
r6675:03a836ca default
parent child Browse files
Show More
@@ -1,639 +1,645 b''
1 1 """
2 2 dirstate.py - working directory tracking for mercurial
3 3
4 4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5
6 6 This software may be used and distributed according to the terms
7 7 of the GNU General Public License, incorporated herein by reference.
8 8 """
9 9
10 10 from node import nullid
11 11 from i18n import _
12 12 import struct, os, bisect, stat, strutil, util, errno, ignore
13 13 import cStringIO, osutil, sys
14 14
15 15 _unknown = ('?', 0, 0, 0)
16 16 _format = ">cllll"
17 17
18 18 class dirstate(object):
19 19
20 20 def __init__(self, opener, ui, root):
21 21 self._opener = opener
22 22 self._root = root
23 23 self._dirty = False
24 24 self._dirtypl = False
25 25 self._ui = ui
26 26
27 27 def __getattr__(self, name):
28 28 if name == '_map':
29 29 self._read()
30 30 return self._map
31 31 elif name == '_copymap':
32 32 self._read()
33 33 return self._copymap
34 34 elif name == '_branch':
35 35 try:
36 36 self._branch = (self._opener("branch").read().strip()
37 37 or "default")
38 38 except IOError:
39 39 self._branch = "default"
40 40 return self._branch
41 41 elif name == '_pl':
42 42 self._pl = [nullid, nullid]
43 43 try:
44 44 st = self._opener("dirstate").read(40)
45 45 if len(st) == 40:
46 46 self._pl = st[:20], st[20:40]
47 47 except IOError, err:
48 48 if err.errno != errno.ENOENT: raise
49 49 return self._pl
50 50 elif name == '_dirs':
51 51 self._dirs = {}
52 52 for f in self._map:
53 53 if self[f] != 'r':
54 54 self._incpath(f)
55 55 return self._dirs
56 56 elif name == '_ignore':
57 57 files = [self._join('.hgignore')]
58 58 for name, path in self._ui.configitems("ui"):
59 59 if name == 'ignore' or name.startswith('ignore.'):
60 60 files.append(os.path.expanduser(path))
61 61 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
62 62 return self._ignore
63 63 elif name == '_slash':
64 64 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
65 65 return self._slash
66 66 elif name == '_checkexec':
67 67 self._checkexec = util.checkexec(self._root)
68 68 return self._checkexec
69 elif name == '_folding':
70 self._folding = not util.checkfolding(self._join('.hg'))
71 return self._folding
69 72 else:
70 73 raise AttributeError, name
71 74
72 75 def _join(self, f):
73 76 return os.path.join(self._root, f)
74 77
78 def folding(self):
79 return self._folding
80
75 81 def getcwd(self):
76 82 cwd = os.getcwd()
77 83 if cwd == self._root: return ''
78 84 # self._root ends with a path separator if self._root is '/' or 'C:\'
79 85 rootsep = self._root
80 86 if not util.endswithsep(rootsep):
81 87 rootsep += os.sep
82 88 if cwd.startswith(rootsep):
83 89 return cwd[len(rootsep):]
84 90 else:
85 91 # we're outside the repo. return an absolute path.
86 92 return cwd
87 93
88 94 def pathto(self, f, cwd=None):
89 95 if cwd is None:
90 96 cwd = self.getcwd()
91 97 path = util.pathto(self._root, cwd, f)
92 98 if self._slash:
93 99 return util.normpath(path)
94 100 return path
95 101
96 102 def __getitem__(self, key):
97 103 ''' current states:
98 104 n normal
99 105 m needs merging
100 106 r marked for removal
101 107 a marked for addition
102 108 ? not tracked'''
103 109 return self._map.get(key, ("?",))[0]
104 110
105 111 def __contains__(self, key):
106 112 return key in self._map
107 113
108 114 def __iter__(self):
109 115 a = self._map.keys()
110 116 a.sort()
111 117 for x in a:
112 118 yield x
113 119
114 120 def parents(self):
115 121 return self._pl
116 122
117 123 def branch(self):
118 124 return self._branch
119 125
120 126 def setparents(self, p1, p2=nullid):
121 127 self._dirty = self._dirtypl = True
122 128 self._pl = p1, p2
123 129
124 130 def setbranch(self, branch):
125 131 self._branch = branch
126 132 self._opener("branch", "w").write(branch + '\n')
127 133
128 134 def _read(self):
129 135 self._map = {}
130 136 self._copymap = {}
131 137 if not self._dirtypl:
132 138 self._pl = [nullid, nullid]
133 139 try:
134 140 st = self._opener("dirstate").read()
135 141 except IOError, err:
136 142 if err.errno != errno.ENOENT: raise
137 143 return
138 144 if not st:
139 145 return
140 146
141 147 if not self._dirtypl:
142 148 self._pl = [st[:20], st[20: 40]]
143 149
144 150 # deref fields so they will be local in loop
145 151 dmap = self._map
146 152 copymap = self._copymap
147 153 unpack = struct.unpack
148 154 e_size = struct.calcsize(_format)
149 155 pos1 = 40
150 156 l = len(st)
151 157
152 158 # the inner loop
153 159 while pos1 < l:
154 160 pos2 = pos1 + e_size
155 161 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
156 162 pos1 = pos2 + e[4]
157 163 f = st[pos2:pos1]
158 164 if '\0' in f:
159 165 f, c = f.split('\0')
160 166 copymap[f] = c
161 167 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
162 168
163 169 def invalidate(self):
164 170 for a in "_map _copymap _branch _pl _dirs _ignore".split():
165 171 if a in self.__dict__:
166 172 delattr(self, a)
167 173 self._dirty = False
168 174
169 175 def copy(self, source, dest):
170 176 self._dirty = True
171 177 self._copymap[dest] = source
172 178
173 179 def copied(self, file):
174 180 return self._copymap.get(file, None)
175 181
176 182 def copies(self):
177 183 return self._copymap
178 184
179 185 def _incpath(self, path):
180 186 c = path.rfind('/')
181 187 if c >= 0:
182 188 dirs = self._dirs
183 189 base = path[:c]
184 190 if base not in dirs:
185 191 self._incpath(base)
186 192 dirs[base] = 1
187 193 else:
188 194 dirs[base] += 1
189 195
190 196 def _decpath(self, path):
191 197 c = path.rfind('/')
192 198 if c >= 0:
193 199 base = path[:c]
194 200 dirs = self._dirs
195 201 if dirs[base] == 1:
196 202 del dirs[base]
197 203 self._decpath(base)
198 204 else:
199 205 dirs[base] -= 1
200 206
201 207 def _incpathcheck(self, f):
202 208 if '\r' in f or '\n' in f:
203 209 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
204 210 % f)
205 211 # shadows
206 212 if f in self._dirs:
207 213 raise util.Abort(_('directory %r already in dirstate') % f)
208 214 for c in strutil.rfindall(f, '/'):
209 215 d = f[:c]
210 216 if d in self._dirs:
211 217 break
212 218 if d in self._map and self[d] != 'r':
213 219 raise util.Abort(_('file %r in dirstate clashes with %r') %
214 220 (d, f))
215 221 self._incpath(f)
216 222
217 223 def _changepath(self, f, newstate, relaxed=False):
218 224 # handle upcoming path changes
219 225 oldstate = self[f]
220 226 if oldstate not in "?r" and newstate in "?r":
221 227 if "_dirs" in self.__dict__:
222 228 self._decpath(f)
223 229 return
224 230 if oldstate in "?r" and newstate not in "?r":
225 231 if relaxed and oldstate == '?':
226 232 # XXX
227 233 # in relaxed mode we assume the caller knows
228 234 # what it is doing, workaround for updating
229 235 # dir-to-file revisions
230 236 if "_dirs" in self.__dict__:
231 237 self._incpath(f)
232 238 return
233 239 self._incpathcheck(f)
234 240 return
235 241
236 242 def normal(self, f):
237 243 'mark a file normal and clean'
238 244 self._dirty = True
239 245 self._changepath(f, 'n', True)
240 246 s = os.lstat(self._join(f))
241 247 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
242 248 if f in self._copymap:
243 249 del self._copymap[f]
244 250
245 251 def normallookup(self, f):
246 252 'mark a file normal, but possibly dirty'
247 253 if self._pl[1] != nullid and f in self._map:
248 254 # if there is a merge going on and the file was either
249 255 # in state 'm' or dirty before being removed, restore that state.
250 256 entry = self._map[f]
251 257 if entry[0] == 'r' and entry[2] in (-1, -2):
252 258 source = self._copymap.get(f)
253 259 if entry[2] == -1:
254 260 self.merge(f)
255 261 elif entry[2] == -2:
256 262 self.normaldirty(f)
257 263 if source:
258 264 self.copy(source, f)
259 265 return
260 266 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
261 267 return
262 268 self._dirty = True
263 269 self._changepath(f, 'n', True)
264 270 self._map[f] = ('n', 0, -1, -1, 0)
265 271 if f in self._copymap:
266 272 del self._copymap[f]
267 273
268 274 def normaldirty(self, f):
269 275 'mark a file normal, but dirty'
270 276 self._dirty = True
271 277 self._changepath(f, 'n', True)
272 278 self._map[f] = ('n', 0, -2, -1, 0)
273 279 if f in self._copymap:
274 280 del self._copymap[f]
275 281
276 282 def add(self, f):
277 283 'mark a file added'
278 284 self._dirty = True
279 285 self._changepath(f, 'a')
280 286 self._map[f] = ('a', 0, -1, -1, 0)
281 287 if f in self._copymap:
282 288 del self._copymap[f]
283 289
284 290 def remove(self, f):
285 291 'mark a file removed'
286 292 self._dirty = True
287 293 self._changepath(f, 'r')
288 294 size = 0
289 295 if self._pl[1] != nullid and f in self._map:
290 296 entry = self._map[f]
291 297 if entry[0] == 'm':
292 298 size = -1
293 299 elif entry[0] == 'n' and entry[2] == -2:
294 300 size = -2
295 301 self._map[f] = ('r', 0, size, 0, 0)
296 302 if size == 0 and f in self._copymap:
297 303 del self._copymap[f]
298 304
299 305 def merge(self, f):
300 306 'mark a file merged'
301 307 self._dirty = True
302 308 s = os.lstat(self._join(f))
303 309 self._changepath(f, 'm', True)
304 310 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
305 311 if f in self._copymap:
306 312 del self._copymap[f]
307 313
308 314 def forget(self, f):
309 315 'forget a file'
310 316 self._dirty = True
311 317 try:
312 318 self._changepath(f, '?')
313 319 del self._map[f]
314 320 except KeyError:
315 321 self._ui.warn(_("not in dirstate: %s\n") % f)
316 322
317 323 def clear(self):
318 324 self._map = {}
319 325 if "_dirs" in self.__dict__:
320 326 delattr(self, "_dirs");
321 327 self._copymap = {}
322 328 self._pl = [nullid, nullid]
323 329 self._dirty = True
324 330
325 331 def rebuild(self, parent, files):
326 332 self.clear()
327 333 for f in files:
328 334 if files.execf(f):
329 335 self._map[f] = ('n', 0777, -1, 0, 0)
330 336 else:
331 337 self._map[f] = ('n', 0666, -1, 0, 0)
332 338 self._pl = (parent, nullid)
333 339 self._dirty = True
334 340
335 341 def write(self):
336 342 if not self._dirty:
337 343 return
338 344 st = self._opener("dirstate", "w", atomictemp=True)
339 345
340 346 try:
341 347 gran = int(self._ui.config('dirstate', 'granularity', 1))
342 348 except ValueError:
343 349 gran = 1
344 350 limit = sys.maxint
345 351 if gran > 0:
346 352 limit = util.fstat(st).st_mtime - gran
347 353
348 354 cs = cStringIO.StringIO()
349 355 copymap = self._copymap
350 356 pack = struct.pack
351 357 write = cs.write
352 358 write("".join(self._pl))
353 359 for f, e in self._map.iteritems():
354 360 if f in copymap:
355 361 f = "%s\0%s" % (f, copymap[f])
356 362 if e[3] > limit and e[0] == 'n':
357 363 e = (e[0], 0, -1, -1, 0)
358 364 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
359 365 write(e)
360 366 write(f)
361 367 st.write(cs.getvalue())
362 368 st.rename()
363 369 self._dirty = self._dirtypl = False
364 370
365 371 def _filter(self, files):
366 372 ret = {}
367 373 unknown = []
368 374
369 375 for x in files:
370 376 if x == '.':
371 377 return self._map.copy()
372 378 if x not in self._map:
373 379 unknown.append(x)
374 380 else:
375 381 ret[x] = self._map[x]
376 382
377 383 if not unknown:
378 384 return ret
379 385
380 386 b = self._map.keys()
381 387 b.sort()
382 388 blen = len(b)
383 389
384 390 for x in unknown:
385 391 bs = bisect.bisect(b, "%s%s" % (x, '/'))
386 392 while bs < blen:
387 393 s = b[bs]
388 394 if len(s) > len(x) and s.startswith(x):
389 395 ret[s] = self._map[s]
390 396 else:
391 397 break
392 398 bs += 1
393 399 return ret
394 400
395 401 def _supported(self, f, mode, verbose=False):
396 402 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
397 403 return True
398 404 if verbose:
399 405 kind = 'unknown'
400 406 if stat.S_ISCHR(mode): kind = _('character device')
401 407 elif stat.S_ISBLK(mode): kind = _('block device')
402 408 elif stat.S_ISFIFO(mode): kind = _('fifo')
403 409 elif stat.S_ISSOCK(mode): kind = _('socket')
404 410 elif stat.S_ISDIR(mode): kind = _('directory')
405 411 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
406 412 % (self.pathto(f), kind))
407 413 return False
408 414
409 415 def _dirignore(self, f):
410 416 if f == '.':
411 417 return False
412 418 if self._ignore(f):
413 419 return True
414 420 for c in strutil.findall(f, '/'):
415 421 if self._ignore(f[:c]):
416 422 return True
417 423 return False
418 424
419 425 def walk(self, match):
420 426 # filter out the src and stat
421 427 for src, f, st in self.statwalk(match):
422 428 yield f
423 429
424 430 def statwalk(self, match, unknown=True, ignored=False):
425 431 '''
426 432 walk recursively through the directory tree, finding all files
427 433 matched by the match function
428 434
429 435 results are yielded in a tuple (src, filename, st), where src
430 436 is one of:
431 437 'f' the file was found in the directory tree
432 438 'm' the file was only in the dirstate and not in the tree
433 439
434 440 and st is the stat result if the file was found in the directory.
435 441 '''
436 442
437 443 def fwarn(f, msg):
438 444 self._ui.warn('%s: %s\n' % (self.pathto(ff), msg))
439 445 return False
440 446 badfn = fwarn
441 447 if hasattr(match, 'bad'):
442 448 badfn = match.bad
443 449
444 450 # walk all files by default
445 451 files = match.files()
446 452 if not files:
447 453 files = ['.']
448 454 dc = self._map.copy()
449 455 else:
450 456 files = util.unique(files)
451 457 dc = self._filter(files)
452 458
453 459 def imatch(file_):
454 460 if file_ not in dc and self._ignore(file_):
455 461 return False
456 462 return match(file_)
457 463
458 464 # TODO: don't walk unknown directories if unknown and ignored are False
459 465 ignore = self._ignore
460 466 dirignore = self._dirignore
461 467 if ignored:
462 468 imatch = match
463 469 ignore = util.never
464 470 dirignore = util.never
465 471
466 472 # self._root may end with a path separator when self._root == '/'
467 473 common_prefix_len = len(self._root)
468 474 if not util.endswithsep(self._root):
469 475 common_prefix_len += 1
470 476
471 477 normpath = util.normpath
472 478 listdir = osutil.listdir
473 479 lstat = os.lstat
474 480 bisect_left = bisect.bisect_left
475 481 isdir = os.path.isdir
476 482 pconvert = util.pconvert
477 483 join = os.path.join
478 484 s_isdir = stat.S_ISDIR
479 485 supported = self._supported
480 486 _join = self._join
481 487 known = {'.hg': 1}
482 488
483 489 # recursion free walker, faster than os.walk.
484 490 def findfiles(s):
485 491 work = [s]
486 492 wadd = work.append
487 493 found = []
488 494 add = found.append
489 495 if hasattr(match, 'dir'):
490 496 match.dir(normpath(s[common_prefix_len:]))
491 497 while work:
492 498 top = work.pop()
493 499 entries = listdir(top, stat=True)
494 500 # nd is the top of the repository dir tree
495 501 nd = normpath(top[common_prefix_len:])
496 502 if nd == '.':
497 503 nd = ''
498 504 else:
499 505 # do not recurse into a repo contained in this
500 506 # one. use bisect to find .hg directory so speed
501 507 # is good on big directory.
502 508 names = [e[0] for e in entries]
503 509 hg = bisect_left(names, '.hg')
504 510 if hg < len(names) and names[hg] == '.hg':
505 511 if isdir(join(top, '.hg')):
506 512 continue
507 513 for f, kind, st in entries:
508 514 np = pconvert(join(nd, f))
509 515 if np in known:
510 516 continue
511 517 known[np] = 1
512 518 p = join(top, f)
513 519 # don't trip over symlinks
514 520 if kind == stat.S_IFDIR:
515 521 if not ignore(np):
516 522 wadd(p)
517 523 if hasattr(match, 'dir'):
518 524 match.dir(np)
519 525 if np in dc and match(np):
520 526 add((np, 'm', st))
521 527 elif imatch(np):
522 528 if supported(np, st.st_mode):
523 529 add((np, 'f', st))
524 530 elif np in dc:
525 531 add((np, 'm', st))
526 532 found.sort()
527 533 return found
528 534
529 535 # step one, find all files that match our criteria
530 536 files.sort()
531 537 for ff in files:
532 538 nf = normpath(ff)
533 539 f = _join(ff)
534 540 try:
535 541 st = lstat(f)
536 542 except OSError, inst:
537 543 found = False
538 544 for fn in dc:
539 545 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
540 546 found = True
541 547 break
542 548 if not found:
543 549 if inst.errno != errno.ENOENT:
544 550 fwarn(ff, inst.strerror)
545 551 elif badfn(ff, inst.strerror) and imatch(nf):
546 552 yield 'f', ff, None
547 553 continue
548 554 if s_isdir(st.st_mode):
549 555 if not dirignore(nf):
550 556 for f, src, st in findfiles(f):
551 557 yield src, f, st
552 558 else:
553 559 if nf in known:
554 560 continue
555 561 known[nf] = 1
556 562 if match(nf):
557 563 if supported(ff, st.st_mode, verbose=True):
558 564 yield 'f', nf, st
559 565 elif ff in dc:
560 566 yield 'm', nf, st
561 567
562 568 # step two run through anything left in the dc hash and yield
563 569 # if we haven't already seen it
564 570 ks = dc.keys()
565 571 ks.sort()
566 572 for k in ks:
567 573 if k in known:
568 574 continue
569 575 known[k] = 1
570 576 if imatch(k):
571 577 yield 'm', k, None
572 578
573 579 def status(self, match, list_ignored, list_clean, list_unknown):
574 580 lookup, modified, added, unknown, ignored = [], [], [], [], []
575 581 removed, deleted, clean = [], [], []
576 582
577 583 _join = self._join
578 584 lstat = os.lstat
579 585 cmap = self._copymap
580 586 dmap = self._map
581 587 ladd = lookup.append
582 588 madd = modified.append
583 589 aadd = added.append
584 590 uadd = unknown.append
585 591 iadd = ignored.append
586 592 radd = removed.append
587 593 dadd = deleted.append
588 594 cadd = clean.append
589 595
590 596 for src, fn, st in self.statwalk(match, unknown=list_unknown,
591 597 ignored=list_ignored):
592 598 if fn not in dmap:
593 599 if (list_ignored or match.exact(fn)) and self._dirignore(fn):
594 600 if list_ignored:
595 601 iadd(fn)
596 602 elif list_unknown:
597 603 uadd(fn)
598 604 continue
599 605
600 606 state, mode, size, time, foo = dmap[fn]
601 607
602 608 if src == 'm':
603 609 nonexistent = True
604 610 if not st:
605 611 try:
606 612 st = lstat(_join(fn))
607 613 except OSError, inst:
608 614 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
609 615 raise
610 616 st = None
611 617 # We need to re-check that it is a valid file
612 618 if st and self._supported(fn, st.st_mode):
613 619 nonexistent = False
614 620 if nonexistent and state in "nma":
615 621 dadd(fn)
616 622 continue
617 623 # check the common case first
618 624 if state == 'n':
619 625 if not st:
620 626 st = lstat(_join(fn))
621 627 if (size >= 0 and
622 628 (size != st.st_size
623 629 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
624 630 or size == -2
625 631 or fn in self._copymap):
626 632 madd(fn)
627 633 elif time != int(st.st_mtime):
628 634 ladd(fn)
629 635 elif list_clean:
630 636 cadd(fn)
631 637 elif state == 'm':
632 638 madd(fn)
633 639 elif state == 'a':
634 640 aadd(fn)
635 641 elif state == 'r':
636 642 radd(fn)
637 643
638 644 return (lookup, modified, added, removed, deleted, unknown, ignored,
639 645 clean)
General Comments 0
You need to be logged in to leave comments. Login now