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