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