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