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