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