##// END OF EJS Templates
ignore: remove .hgignore from ignore list if nonexistent...
Durham Goode -
r25163:3139900f default
parent child Browse files
Show More
@@ -1,979 +1,981 b''
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid
9 9 from i18n import _
10 10 import scmutil, util, ignore, osutil, parsers, encoding, pathutil
11 11 import os, stat, errno
12 12
13 13 propertycache = util.propertycache
14 14 filecache = scmutil.filecache
15 15 _rangemask = 0x7fffffff
16 16
17 17 dirstatetuple = parsers.dirstatetuple
18 18
19 19 class repocache(filecache):
20 20 """filecache for files in .hg/"""
21 21 def join(self, obj, fname):
22 22 return obj._opener.join(fname)
23 23
24 24 class rootcache(filecache):
25 25 """filecache for files in the repository root"""
26 26 def join(self, obj, fname):
27 27 return obj._join(fname)
28 28
29 29 class dirstate(object):
30 30
31 31 def __init__(self, opener, ui, root, validate):
32 32 '''Create a new dirstate object.
33 33
34 34 opener is an open()-like callable that can be used to open the
35 35 dirstate file; root is the root of the directory tracked by
36 36 the dirstate.
37 37 '''
38 38 self._opener = opener
39 39 self._validate = validate
40 40 self._root = root
41 41 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
42 42 # UNC path pointing to root share (issue4557)
43 43 self._rootdir = pathutil.normasprefix(root)
44 44 self._dirty = False
45 45 self._dirtypl = False
46 46 self._lastnormaltime = 0
47 47 self._ui = ui
48 48 self._filecache = {}
49 49 self._parentwriters = 0
50 50
51 51 def beginparentchange(self):
52 52 '''Marks the beginning of a set of changes that involve changing
53 53 the dirstate parents. If there is an exception during this time,
54 54 the dirstate will not be written when the wlock is released. This
55 55 prevents writing an incoherent dirstate where the parent doesn't
56 56 match the contents.
57 57 '''
58 58 self._parentwriters += 1
59 59
60 60 def endparentchange(self):
61 61 '''Marks the end of a set of changes that involve changing the
62 62 dirstate parents. Once all parent changes have been marked done,
63 63 the wlock will be free to write the dirstate on release.
64 64 '''
65 65 if self._parentwriters > 0:
66 66 self._parentwriters -= 1
67 67
68 68 def pendingparentchange(self):
69 69 '''Returns true if the dirstate is in the middle of a set of changes
70 70 that modify the dirstate parent.
71 71 '''
72 72 return self._parentwriters > 0
73 73
74 74 @propertycache
75 75 def _map(self):
76 76 '''Return the dirstate contents as a map from filename to
77 77 (state, mode, size, time).'''
78 78 self._read()
79 79 return self._map
80 80
81 81 @propertycache
82 82 def _copymap(self):
83 83 self._read()
84 84 return self._copymap
85 85
86 86 @propertycache
87 87 def _filefoldmap(self):
88 88 try:
89 89 makefilefoldmap = parsers.make_file_foldmap
90 90 except AttributeError:
91 91 pass
92 92 else:
93 93 return makefilefoldmap(self._map, util.normcasespec,
94 94 util.normcasefallback)
95 95
96 96 f = {}
97 97 normcase = util.normcase
98 98 for name, s in self._map.iteritems():
99 99 if s[0] != 'r':
100 100 f[normcase(name)] = name
101 101 f['.'] = '.' # prevents useless util.fspath() invocation
102 102 return f
103 103
104 104 @propertycache
105 105 def _dirfoldmap(self):
106 106 f = {}
107 107 normcase = util.normcase
108 108 for name in self._dirs:
109 109 f[normcase(name)] = name
110 110 return f
111 111
112 112 @repocache('branch')
113 113 def _branch(self):
114 114 try:
115 115 return self._opener.read("branch").strip() or "default"
116 116 except IOError, inst:
117 117 if inst.errno != errno.ENOENT:
118 118 raise
119 119 return "default"
120 120
121 121 @propertycache
122 122 def _pl(self):
123 123 try:
124 124 fp = self._opener("dirstate")
125 125 st = fp.read(40)
126 126 fp.close()
127 127 l = len(st)
128 128 if l == 40:
129 129 return st[:20], st[20:40]
130 130 elif l > 0 and l < 40:
131 131 raise util.Abort(_('working directory state appears damaged!'))
132 132 except IOError, err:
133 133 if err.errno != errno.ENOENT:
134 134 raise
135 135 return [nullid, nullid]
136 136
137 137 @propertycache
138 138 def _dirs(self):
139 139 return util.dirs(self._map, 'r')
140 140
141 141 def dirs(self):
142 142 return self._dirs
143 143
144 144 @rootcache('.hgignore')
145 145 def _ignore(self):
146 files = [self._join('.hgignore')]
146 files = []
147 if os.path.exists(self._join('.hgignore')):
148 files.append(self._join('.hgignore'))
147 149 for name, path in self._ui.configitems("ui"):
148 150 if name == 'ignore' or name.startswith('ignore.'):
149 151 # we need to use os.path.join here rather than self._join
150 152 # because path is arbitrary and user-specified
151 153 files.append(os.path.join(self._rootdir, util.expandpath(path)))
152 154 return ignore.ignore(self._root, files, self._ui.warn)
153 155
154 156 @propertycache
155 157 def _slash(self):
156 158 return self._ui.configbool('ui', 'slash') and os.sep != '/'
157 159
158 160 @propertycache
159 161 def _checklink(self):
160 162 return util.checklink(self._root)
161 163
162 164 @propertycache
163 165 def _checkexec(self):
164 166 return util.checkexec(self._root)
165 167
166 168 @propertycache
167 169 def _checkcase(self):
168 170 return not util.checkcase(self._join('.hg'))
169 171
170 172 def _join(self, f):
171 173 # much faster than os.path.join()
172 174 # it's safe because f is always a relative path
173 175 return self._rootdir + f
174 176
175 177 def flagfunc(self, buildfallback):
176 178 if self._checklink and self._checkexec:
177 179 def f(x):
178 180 try:
179 181 st = os.lstat(self._join(x))
180 182 if util.statislink(st):
181 183 return 'l'
182 184 if util.statisexec(st):
183 185 return 'x'
184 186 except OSError:
185 187 pass
186 188 return ''
187 189 return f
188 190
189 191 fallback = buildfallback()
190 192 if self._checklink:
191 193 def f(x):
192 194 if os.path.islink(self._join(x)):
193 195 return 'l'
194 196 if 'x' in fallback(x):
195 197 return 'x'
196 198 return ''
197 199 return f
198 200 if self._checkexec:
199 201 def f(x):
200 202 if 'l' in fallback(x):
201 203 return 'l'
202 204 if util.isexec(self._join(x)):
203 205 return 'x'
204 206 return ''
205 207 return f
206 208 else:
207 209 return fallback
208 210
209 211 @propertycache
210 212 def _cwd(self):
211 213 return os.getcwd()
212 214
213 215 def getcwd(self):
214 216 cwd = self._cwd
215 217 if cwd == self._root:
216 218 return ''
217 219 # self._root ends with a path separator if self._root is '/' or 'C:\'
218 220 rootsep = self._root
219 221 if not util.endswithsep(rootsep):
220 222 rootsep += os.sep
221 223 if cwd.startswith(rootsep):
222 224 return cwd[len(rootsep):]
223 225 else:
224 226 # we're outside the repo. return an absolute path.
225 227 return cwd
226 228
227 229 def pathto(self, f, cwd=None):
228 230 if cwd is None:
229 231 cwd = self.getcwd()
230 232 path = util.pathto(self._root, cwd, f)
231 233 if self._slash:
232 234 return util.pconvert(path)
233 235 return path
234 236
235 237 def __getitem__(self, key):
236 238 '''Return the current state of key (a filename) in the dirstate.
237 239
238 240 States are:
239 241 n normal
240 242 m needs merging
241 243 r marked for removal
242 244 a marked for addition
243 245 ? not tracked
244 246 '''
245 247 return self._map.get(key, ("?",))[0]
246 248
247 249 def __contains__(self, key):
248 250 return key in self._map
249 251
250 252 def __iter__(self):
251 253 for x in sorted(self._map):
252 254 yield x
253 255
254 256 def iteritems(self):
255 257 return self._map.iteritems()
256 258
257 259 def parents(self):
258 260 return [self._validate(p) for p in self._pl]
259 261
260 262 def p1(self):
261 263 return self._validate(self._pl[0])
262 264
263 265 def p2(self):
264 266 return self._validate(self._pl[1])
265 267
266 268 def branch(self):
267 269 return encoding.tolocal(self._branch)
268 270
269 271 def setparents(self, p1, p2=nullid):
270 272 """Set dirstate parents to p1 and p2.
271 273
272 274 When moving from two parents to one, 'm' merged entries a
273 275 adjusted to normal and previous copy records discarded and
274 276 returned by the call.
275 277
276 278 See localrepo.setparents()
277 279 """
278 280 if self._parentwriters == 0:
279 281 raise ValueError("cannot set dirstate parent without "
280 282 "calling dirstate.beginparentchange")
281 283
282 284 self._dirty = self._dirtypl = True
283 285 oldp2 = self._pl[1]
284 286 self._pl = p1, p2
285 287 copies = {}
286 288 if oldp2 != nullid and p2 == nullid:
287 289 for f, s in self._map.iteritems():
288 290 # Discard 'm' markers when moving away from a merge state
289 291 if s[0] == 'm':
290 292 if f in self._copymap:
291 293 copies[f] = self._copymap[f]
292 294 self.normallookup(f)
293 295 # Also fix up otherparent markers
294 296 elif s[0] == 'n' and s[2] == -2:
295 297 if f in self._copymap:
296 298 copies[f] = self._copymap[f]
297 299 self.add(f)
298 300 return copies
299 301
300 302 def setbranch(self, branch):
301 303 self._branch = encoding.fromlocal(branch)
302 304 f = self._opener('branch', 'w', atomictemp=True)
303 305 try:
304 306 f.write(self._branch + '\n')
305 307 f.close()
306 308
307 309 # make sure filecache has the correct stat info for _branch after
308 310 # replacing the underlying file
309 311 ce = self._filecache['_branch']
310 312 if ce:
311 313 ce.refresh()
312 314 except: # re-raises
313 315 f.discard()
314 316 raise
315 317
316 318 def _read(self):
317 319 self._map = {}
318 320 self._copymap = {}
319 321 try:
320 322 st = self._opener.read("dirstate")
321 323 except IOError, err:
322 324 if err.errno != errno.ENOENT:
323 325 raise
324 326 return
325 327 if not st:
326 328 return
327 329
328 330 # Python's garbage collector triggers a GC each time a certain number
329 331 # of container objects (the number being defined by
330 332 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
331 333 # for each file in the dirstate. The C version then immediately marks
332 334 # them as not to be tracked by the collector. However, this has no
333 335 # effect on when GCs are triggered, only on what objects the GC looks
334 336 # into. This means that O(number of files) GCs are unavoidable.
335 337 # Depending on when in the process's lifetime the dirstate is parsed,
336 338 # this can get very expensive. As a workaround, disable GC while
337 339 # parsing the dirstate.
338 340 #
339 341 # (we cannot decorate the function directly since it is in a C module)
340 342 parse_dirstate = util.nogc(parsers.parse_dirstate)
341 343 p = parse_dirstate(self._map, self._copymap, st)
342 344 if not self._dirtypl:
343 345 self._pl = p
344 346
345 347 def invalidate(self):
346 348 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
347 349 "_pl", "_dirs", "_ignore"):
348 350 if a in self.__dict__:
349 351 delattr(self, a)
350 352 self._lastnormaltime = 0
351 353 self._dirty = False
352 354 self._parentwriters = 0
353 355
354 356 def copy(self, source, dest):
355 357 """Mark dest as a copy of source. Unmark dest if source is None."""
356 358 if source == dest:
357 359 return
358 360 self._dirty = True
359 361 if source is not None:
360 362 self._copymap[dest] = source
361 363 elif dest in self._copymap:
362 364 del self._copymap[dest]
363 365
364 366 def copied(self, file):
365 367 return self._copymap.get(file, None)
366 368
367 369 def copies(self):
368 370 return self._copymap
369 371
370 372 def _droppath(self, f):
371 373 if self[f] not in "?r" and "_dirs" in self.__dict__:
372 374 self._dirs.delpath(f)
373 375
374 376 def _addpath(self, f, state, mode, size, mtime):
375 377 oldstate = self[f]
376 378 if state == 'a' or oldstate == 'r':
377 379 scmutil.checkfilename(f)
378 380 if f in self._dirs:
379 381 raise util.Abort(_('directory %r already in dirstate') % f)
380 382 # shadows
381 383 for d in util.finddirs(f):
382 384 if d in self._dirs:
383 385 break
384 386 if d in self._map and self[d] != 'r':
385 387 raise util.Abort(
386 388 _('file %r in dirstate clashes with %r') % (d, f))
387 389 if oldstate in "?r" and "_dirs" in self.__dict__:
388 390 self._dirs.addpath(f)
389 391 self._dirty = True
390 392 self._map[f] = dirstatetuple(state, mode, size, mtime)
391 393
392 394 def normal(self, f):
393 395 '''Mark a file normal and clean.'''
394 396 s = os.lstat(self._join(f))
395 397 mtime = int(s.st_mtime)
396 398 self._addpath(f, 'n', s.st_mode,
397 399 s.st_size & _rangemask, mtime & _rangemask)
398 400 if f in self._copymap:
399 401 del self._copymap[f]
400 402 if mtime > self._lastnormaltime:
401 403 # Remember the most recent modification timeslot for status(),
402 404 # to make sure we won't miss future size-preserving file content
403 405 # modifications that happen within the same timeslot.
404 406 self._lastnormaltime = mtime
405 407
406 408 def normallookup(self, f):
407 409 '''Mark a file normal, but possibly dirty.'''
408 410 if self._pl[1] != nullid and f in self._map:
409 411 # if there is a merge going on and the file was either
410 412 # in state 'm' (-1) or coming from other parent (-2) before
411 413 # being removed, restore that state.
412 414 entry = self._map[f]
413 415 if entry[0] == 'r' and entry[2] in (-1, -2):
414 416 source = self._copymap.get(f)
415 417 if entry[2] == -1:
416 418 self.merge(f)
417 419 elif entry[2] == -2:
418 420 self.otherparent(f)
419 421 if source:
420 422 self.copy(source, f)
421 423 return
422 424 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
423 425 return
424 426 self._addpath(f, 'n', 0, -1, -1)
425 427 if f in self._copymap:
426 428 del self._copymap[f]
427 429
428 430 def otherparent(self, f):
429 431 '''Mark as coming from the other parent, always dirty.'''
430 432 if self._pl[1] == nullid:
431 433 raise util.Abort(_("setting %r to other parent "
432 434 "only allowed in merges") % f)
433 435 if f in self and self[f] == 'n':
434 436 # merge-like
435 437 self._addpath(f, 'm', 0, -2, -1)
436 438 else:
437 439 # add-like
438 440 self._addpath(f, 'n', 0, -2, -1)
439 441
440 442 if f in self._copymap:
441 443 del self._copymap[f]
442 444
443 445 def add(self, f):
444 446 '''Mark a file added.'''
445 447 self._addpath(f, 'a', 0, -1, -1)
446 448 if f in self._copymap:
447 449 del self._copymap[f]
448 450
449 451 def remove(self, f):
450 452 '''Mark a file removed.'''
451 453 self._dirty = True
452 454 self._droppath(f)
453 455 size = 0
454 456 if self._pl[1] != nullid and f in self._map:
455 457 # backup the previous state
456 458 entry = self._map[f]
457 459 if entry[0] == 'm': # merge
458 460 size = -1
459 461 elif entry[0] == 'n' and entry[2] == -2: # other parent
460 462 size = -2
461 463 self._map[f] = dirstatetuple('r', 0, size, 0)
462 464 if size == 0 and f in self._copymap:
463 465 del self._copymap[f]
464 466
465 467 def merge(self, f):
466 468 '''Mark a file merged.'''
467 469 if self._pl[1] == nullid:
468 470 return self.normallookup(f)
469 471 return self.otherparent(f)
470 472
471 473 def drop(self, f):
472 474 '''Drop a file from the dirstate'''
473 475 if f in self._map:
474 476 self._dirty = True
475 477 self._droppath(f)
476 478 del self._map[f]
477 479
478 480 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
479 481 if exists is None:
480 482 exists = os.path.lexists(os.path.join(self._root, path))
481 483 if not exists:
482 484 # Maybe a path component exists
483 485 if not ignoremissing and '/' in path:
484 486 d, f = path.rsplit('/', 1)
485 487 d = self._normalize(d, False, ignoremissing, None)
486 488 folded = d + "/" + f
487 489 else:
488 490 # No path components, preserve original case
489 491 folded = path
490 492 else:
491 493 # recursively normalize leading directory components
492 494 # against dirstate
493 495 if '/' in normed:
494 496 d, f = normed.rsplit('/', 1)
495 497 d = self._normalize(d, False, ignoremissing, True)
496 498 r = self._root + "/" + d
497 499 folded = d + "/" + util.fspath(f, r)
498 500 else:
499 501 folded = util.fspath(normed, self._root)
500 502 storemap[normed] = folded
501 503
502 504 return folded
503 505
504 506 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
505 507 normed = util.normcase(path)
506 508 folded = self._filefoldmap.get(normed, None)
507 509 if folded is None:
508 510 if isknown:
509 511 folded = path
510 512 else:
511 513 folded = self._discoverpath(path, normed, ignoremissing, exists,
512 514 self._filefoldmap)
513 515 return folded
514 516
515 517 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
516 518 normed = util.normcase(path)
517 519 folded = self._filefoldmap.get(normed, None)
518 520 if folded is None:
519 521 folded = self._dirfoldmap.get(normed, None)
520 522 if folded is None:
521 523 if isknown:
522 524 folded = path
523 525 else:
524 526 # store discovered result in dirfoldmap so that future
525 527 # normalizefile calls don't start matching directories
526 528 folded = self._discoverpath(path, normed, ignoremissing, exists,
527 529 self._dirfoldmap)
528 530 return folded
529 531
530 532 def normalize(self, path, isknown=False, ignoremissing=False):
531 533 '''
532 534 normalize the case of a pathname when on a casefolding filesystem
533 535
534 536 isknown specifies whether the filename came from walking the
535 537 disk, to avoid extra filesystem access.
536 538
537 539 If ignoremissing is True, missing path are returned
538 540 unchanged. Otherwise, we try harder to normalize possibly
539 541 existing path components.
540 542
541 543 The normalized case is determined based on the following precedence:
542 544
543 545 - version of name already stored in the dirstate
544 546 - version of name stored on disk
545 547 - version provided via command arguments
546 548 '''
547 549
548 550 if self._checkcase:
549 551 return self._normalize(path, isknown, ignoremissing)
550 552 return path
551 553
552 554 def clear(self):
553 555 self._map = {}
554 556 if "_dirs" in self.__dict__:
555 557 delattr(self, "_dirs")
556 558 self._copymap = {}
557 559 self._pl = [nullid, nullid]
558 560 self._lastnormaltime = 0
559 561 self._dirty = True
560 562
561 563 def rebuild(self, parent, allfiles, changedfiles=None):
562 564 changedfiles = changedfiles or allfiles
563 565 oldmap = self._map
564 566 self.clear()
565 567 for f in allfiles:
566 568 if f not in changedfiles:
567 569 self._map[f] = oldmap[f]
568 570 else:
569 571 if 'x' in allfiles.flags(f):
570 572 self._map[f] = dirstatetuple('n', 0777, -1, 0)
571 573 else:
572 574 self._map[f] = dirstatetuple('n', 0666, -1, 0)
573 575 self._pl = (parent, nullid)
574 576 self._dirty = True
575 577
576 578 def write(self):
577 579 if not self._dirty:
578 580 return
579 581
580 582 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
581 583 # timestamp of each entries in dirstate, because of 'now > mtime'
582 584 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
583 585 if delaywrite > 0:
584 586 import time # to avoid useless import
585 587 time.sleep(delaywrite)
586 588
587 589 st = self._opener("dirstate", "w", atomictemp=True)
588 590 # use the modification time of the newly created temporary file as the
589 591 # filesystem's notion of 'now'
590 592 now = util.fstat(st).st_mtime
591 593 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
592 594 st.close()
593 595 self._lastnormaltime = 0
594 596 self._dirty = self._dirtypl = False
595 597
596 598 def _dirignore(self, f):
597 599 if f == '.':
598 600 return False
599 601 if self._ignore(f):
600 602 return True
601 603 for p in util.finddirs(f):
602 604 if self._ignore(p):
603 605 return True
604 606 return False
605 607
606 608 def _walkexplicit(self, match, subrepos):
607 609 '''Get stat data about the files explicitly specified by match.
608 610
609 611 Return a triple (results, dirsfound, dirsnotfound).
610 612 - results is a mapping from filename to stat result. It also contains
611 613 listings mapping subrepos and .hg to None.
612 614 - dirsfound is a list of files found to be directories.
613 615 - dirsnotfound is a list of files that the dirstate thinks are
614 616 directories and that were not found.'''
615 617
616 618 def badtype(mode):
617 619 kind = _('unknown')
618 620 if stat.S_ISCHR(mode):
619 621 kind = _('character device')
620 622 elif stat.S_ISBLK(mode):
621 623 kind = _('block device')
622 624 elif stat.S_ISFIFO(mode):
623 625 kind = _('fifo')
624 626 elif stat.S_ISSOCK(mode):
625 627 kind = _('socket')
626 628 elif stat.S_ISDIR(mode):
627 629 kind = _('directory')
628 630 return _('unsupported file type (type is %s)') % kind
629 631
630 632 matchedir = match.explicitdir
631 633 badfn = match.bad
632 634 dmap = self._map
633 635 lstat = os.lstat
634 636 getkind = stat.S_IFMT
635 637 dirkind = stat.S_IFDIR
636 638 regkind = stat.S_IFREG
637 639 lnkkind = stat.S_IFLNK
638 640 join = self._join
639 641 dirsfound = []
640 642 foundadd = dirsfound.append
641 643 dirsnotfound = []
642 644 notfoundadd = dirsnotfound.append
643 645
644 646 if not match.isexact() and self._checkcase:
645 647 normalize = self._normalize
646 648 else:
647 649 normalize = None
648 650
649 651 files = sorted(match.files())
650 652 subrepos.sort()
651 653 i, j = 0, 0
652 654 while i < len(files) and j < len(subrepos):
653 655 subpath = subrepos[j] + "/"
654 656 if files[i] < subpath:
655 657 i += 1
656 658 continue
657 659 while i < len(files) and files[i].startswith(subpath):
658 660 del files[i]
659 661 j += 1
660 662
661 663 if not files or '.' in files:
662 664 files = ['.']
663 665 results = dict.fromkeys(subrepos)
664 666 results['.hg'] = None
665 667
666 668 alldirs = None
667 669 for ff in files:
668 670 # constructing the foldmap is expensive, so don't do it for the
669 671 # common case where files is ['.']
670 672 if normalize and ff != '.':
671 673 nf = normalize(ff, False, True)
672 674 else:
673 675 nf = ff
674 676 if nf in results:
675 677 continue
676 678
677 679 try:
678 680 st = lstat(join(nf))
679 681 kind = getkind(st.st_mode)
680 682 if kind == dirkind:
681 683 if nf in dmap:
682 684 # file replaced by dir on disk but still in dirstate
683 685 results[nf] = None
684 686 if matchedir:
685 687 matchedir(nf)
686 688 foundadd((nf, ff))
687 689 elif kind == regkind or kind == lnkkind:
688 690 results[nf] = st
689 691 else:
690 692 badfn(ff, badtype(kind))
691 693 if nf in dmap:
692 694 results[nf] = None
693 695 except OSError, inst: # nf not found on disk - it is dirstate only
694 696 if nf in dmap: # does it exactly match a missing file?
695 697 results[nf] = None
696 698 else: # does it match a missing directory?
697 699 if alldirs is None:
698 700 alldirs = util.dirs(dmap)
699 701 if nf in alldirs:
700 702 if matchedir:
701 703 matchedir(nf)
702 704 notfoundadd(nf)
703 705 else:
704 706 badfn(ff, inst.strerror)
705 707
706 708 return results, dirsfound, dirsnotfound
707 709
708 710 def walk(self, match, subrepos, unknown, ignored, full=True):
709 711 '''
710 712 Walk recursively through the directory tree, finding all files
711 713 matched by match.
712 714
713 715 If full is False, maybe skip some known-clean files.
714 716
715 717 Return a dict mapping filename to stat-like object (either
716 718 mercurial.osutil.stat instance or return value of os.stat()).
717 719
718 720 '''
719 721 # full is a flag that extensions that hook into walk can use -- this
720 722 # implementation doesn't use it at all. This satisfies the contract
721 723 # because we only guarantee a "maybe".
722 724
723 725 if ignored:
724 726 ignore = util.never
725 727 dirignore = util.never
726 728 elif unknown:
727 729 ignore = self._ignore
728 730 dirignore = self._dirignore
729 731 else:
730 732 # if not unknown and not ignored, drop dir recursion and step 2
731 733 ignore = util.always
732 734 dirignore = util.always
733 735
734 736 matchfn = match.matchfn
735 737 matchalways = match.always()
736 738 matchtdir = match.traversedir
737 739 dmap = self._map
738 740 listdir = osutil.listdir
739 741 lstat = os.lstat
740 742 dirkind = stat.S_IFDIR
741 743 regkind = stat.S_IFREG
742 744 lnkkind = stat.S_IFLNK
743 745 join = self._join
744 746
745 747 exact = skipstep3 = False
746 748 if match.isexact(): # match.exact
747 749 exact = True
748 750 dirignore = util.always # skip step 2
749 751 elif match.files() and not match.anypats(): # match.match, no patterns
750 752 skipstep3 = True
751 753
752 754 if not exact and self._checkcase:
753 755 normalize = self._normalize
754 756 normalizefile = self._normalizefile
755 757 skipstep3 = False
756 758 else:
757 759 normalize = self._normalize
758 760 normalizefile = None
759 761
760 762 # step 1: find all explicit files
761 763 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
762 764
763 765 skipstep3 = skipstep3 and not (work or dirsnotfound)
764 766 work = [d for d in work if not dirignore(d[0])]
765 767
766 768 # step 2: visit subdirectories
767 769 def traverse(work, alreadynormed):
768 770 wadd = work.append
769 771 while work:
770 772 nd = work.pop()
771 773 skip = None
772 774 if nd == '.':
773 775 nd = ''
774 776 else:
775 777 skip = '.hg'
776 778 try:
777 779 entries = listdir(join(nd), stat=True, skip=skip)
778 780 except OSError, inst:
779 781 if inst.errno in (errno.EACCES, errno.ENOENT):
780 782 match.bad(self.pathto(nd), inst.strerror)
781 783 continue
782 784 raise
783 785 for f, kind, st in entries:
784 786 if normalizefile:
785 787 # even though f might be a directory, we're only
786 788 # interested in comparing it to files currently in the
787 789 # dmap -- therefore normalizefile is enough
788 790 nf = normalizefile(nd and (nd + "/" + f) or f, True,
789 791 True)
790 792 else:
791 793 nf = nd and (nd + "/" + f) or f
792 794 if nf not in results:
793 795 if kind == dirkind:
794 796 if not ignore(nf):
795 797 if matchtdir:
796 798 matchtdir(nf)
797 799 wadd(nf)
798 800 if nf in dmap and (matchalways or matchfn(nf)):
799 801 results[nf] = None
800 802 elif kind == regkind or kind == lnkkind:
801 803 if nf in dmap:
802 804 if matchalways or matchfn(nf):
803 805 results[nf] = st
804 806 elif ((matchalways or matchfn(nf))
805 807 and not ignore(nf)):
806 808 # unknown file -- normalize if necessary
807 809 if not alreadynormed:
808 810 nf = normalize(nf, False, True)
809 811 results[nf] = st
810 812 elif nf in dmap and (matchalways or matchfn(nf)):
811 813 results[nf] = None
812 814
813 815 for nd, d in work:
814 816 # alreadynormed means that processwork doesn't have to do any
815 817 # expensive directory normalization
816 818 alreadynormed = not normalize or nd == d
817 819 traverse([d], alreadynormed)
818 820
819 821 for s in subrepos:
820 822 del results[s]
821 823 del results['.hg']
822 824
823 825 # step 3: visit remaining files from dmap
824 826 if not skipstep3 and not exact:
825 827 # If a dmap file is not in results yet, it was either
826 828 # a) not matching matchfn b) ignored, c) missing, or d) under a
827 829 # symlink directory.
828 830 if not results and matchalways:
829 831 visit = dmap.keys()
830 832 else:
831 833 visit = [f for f in dmap if f not in results and matchfn(f)]
832 834 visit.sort()
833 835
834 836 if unknown:
835 837 # unknown == True means we walked all dirs under the roots
836 838 # that wasn't ignored, and everything that matched was stat'ed
837 839 # and is already in results.
838 840 # The rest must thus be ignored or under a symlink.
839 841 audit_path = pathutil.pathauditor(self._root)
840 842
841 843 for nf in iter(visit):
842 844 # If a stat for the same file was already added with a
843 845 # different case, don't add one for this, since that would
844 846 # make it appear as if the file exists under both names
845 847 # on disk.
846 848 if (normalizefile and
847 849 normalizefile(nf, True, True) in results):
848 850 results[nf] = None
849 851 # Report ignored items in the dmap as long as they are not
850 852 # under a symlink directory.
851 853 elif audit_path.check(nf):
852 854 try:
853 855 results[nf] = lstat(join(nf))
854 856 # file was just ignored, no links, and exists
855 857 except OSError:
856 858 # file doesn't exist
857 859 results[nf] = None
858 860 else:
859 861 # It's either missing or under a symlink directory
860 862 # which we in this case report as missing
861 863 results[nf] = None
862 864 else:
863 865 # We may not have walked the full directory tree above,
864 866 # so stat and check everything we missed.
865 867 nf = iter(visit).next
866 868 for st in util.statfiles([join(i) for i in visit]):
867 869 results[nf()] = st
868 870 return results
869 871
870 872 def status(self, match, subrepos, ignored, clean, unknown):
871 873 '''Determine the status of the working copy relative to the
872 874 dirstate and return a pair of (unsure, status), where status is of type
873 875 scmutil.status and:
874 876
875 877 unsure:
876 878 files that might have been modified since the dirstate was
877 879 written, but need to be read to be sure (size is the same
878 880 but mtime differs)
879 881 status.modified:
880 882 files that have definitely been modified since the dirstate
881 883 was written (different size or mode)
882 884 status.clean:
883 885 files that have definitely not been modified since the
884 886 dirstate was written
885 887 '''
886 888 listignored, listclean, listunknown = ignored, clean, unknown
887 889 lookup, modified, added, unknown, ignored = [], [], [], [], []
888 890 removed, deleted, clean = [], [], []
889 891
890 892 dmap = self._map
891 893 ladd = lookup.append # aka "unsure"
892 894 madd = modified.append
893 895 aadd = added.append
894 896 uadd = unknown.append
895 897 iadd = ignored.append
896 898 radd = removed.append
897 899 dadd = deleted.append
898 900 cadd = clean.append
899 901 mexact = match.exact
900 902 dirignore = self._dirignore
901 903 checkexec = self._checkexec
902 904 copymap = self._copymap
903 905 lastnormaltime = self._lastnormaltime
904 906
905 907 # We need to do full walks when either
906 908 # - we're listing all clean files, or
907 909 # - match.traversedir does something, because match.traversedir should
908 910 # be called for every dir in the working dir
909 911 full = listclean or match.traversedir is not None
910 912 for fn, st in self.walk(match, subrepos, listunknown, listignored,
911 913 full=full).iteritems():
912 914 if fn not in dmap:
913 915 if (listignored or mexact(fn)) and dirignore(fn):
914 916 if listignored:
915 917 iadd(fn)
916 918 else:
917 919 uadd(fn)
918 920 continue
919 921
920 922 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
921 923 # written like that for performance reasons. dmap[fn] is not a
922 924 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
923 925 # opcode has fast paths when the value to be unpacked is a tuple or
924 926 # a list, but falls back to creating a full-fledged iterator in
925 927 # general. That is much slower than simply accessing and storing the
926 928 # tuple members one by one.
927 929 t = dmap[fn]
928 930 state = t[0]
929 931 mode = t[1]
930 932 size = t[2]
931 933 time = t[3]
932 934
933 935 if not st and state in "nma":
934 936 dadd(fn)
935 937 elif state == 'n':
936 938 mtime = int(st.st_mtime)
937 939 if (size >= 0 and
938 940 ((size != st.st_size and size != st.st_size & _rangemask)
939 941 or ((mode ^ st.st_mode) & 0100 and checkexec))
940 942 or size == -2 # other parent
941 943 or fn in copymap):
942 944 madd(fn)
943 945 elif time != mtime and time != mtime & _rangemask:
944 946 ladd(fn)
945 947 elif mtime == lastnormaltime:
946 948 # fn may have just been marked as normal and it may have
947 949 # changed in the same second without changing its size.
948 950 # This can happen if we quickly do multiple commits.
949 951 # Force lookup, so we don't miss such a racy file change.
950 952 ladd(fn)
951 953 elif listclean:
952 954 cadd(fn)
953 955 elif state == 'm':
954 956 madd(fn)
955 957 elif state == 'a':
956 958 aadd(fn)
957 959 elif state == 'r':
958 960 radd(fn)
959 961
960 962 return (lookup, scmutil.status(modified, added, removed, deleted,
961 963 unknown, ignored, clean))
962 964
963 965 def matches(self, match):
964 966 '''
965 967 return files in the dirstate (in whatever state) filtered by match
966 968 '''
967 969 dmap = self._map
968 970 if match.always():
969 971 return dmap.keys()
970 972 files = match.files()
971 973 if match.isexact():
972 974 # fast path -- filter the other way around, since typically files is
973 975 # much smaller than dmap
974 976 return [f for f in files if f in dmap]
975 977 if not match.anypats() and all(fn in dmap for fn in files):
976 978 # fast path -- all the values are known to be files, so just return
977 979 # that
978 980 return list(files)
979 981 return [f for f in dmap if match(f)]
@@ -1,122 +1,120 b''
1 1 # ignore.py - ignored file handling for mercurial
2 2 #
3 3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import util, match
10 10 import re
11 11
12 12 _commentre = None
13 13
14 14 def ignorepats(lines):
15 15 '''parse lines (iterable) of .hgignore text, returning a tuple of
16 16 (patterns, parse errors). These patterns should be given to compile()
17 17 to be validated and converted into a match function.'''
18 18 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
19 19 syntax = 'relre:'
20 20 patterns = []
21 21 warnings = []
22 22
23 23 for line in lines:
24 24 if "#" in line:
25 25 global _commentre
26 26 if not _commentre:
27 27 _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
28 28 # remove comments prefixed by an even number of escapes
29 29 line = _commentre.sub(r'\1', line)
30 30 # fixup properly escaped comments that survived the above
31 31 line = line.replace("\\#", "#")
32 32 line = line.rstrip()
33 33 if not line:
34 34 continue
35 35
36 36 if line.startswith('syntax:'):
37 37 s = line[7:].strip()
38 38 try:
39 39 syntax = syntaxes[s]
40 40 except KeyError:
41 41 warnings.append(_("ignoring invalid syntax '%s'") % s)
42 42 continue
43 43
44 44 linesyntax = syntax
45 45 for s, rels in syntaxes.iteritems():
46 46 if line.startswith(rels):
47 47 linesyntax = rels
48 48 line = line[len(rels):]
49 49 break
50 50 elif line.startswith(s+':'):
51 51 linesyntax = rels
52 52 line = line[len(s) + 1:]
53 53 break
54 54 patterns.append(linesyntax + line)
55 55
56 56 return patterns, warnings
57 57
58 def readignorefile(filepath, warn, skipwarning=False):
58 def readignorefile(filepath, warn):
59 59 try:
60 60 pats = []
61 61 fp = open(filepath)
62 62 pats, warnings = ignorepats(fp)
63 63 fp.close()
64 64 for warning in warnings:
65 65 warn("%s: %s\n" % (filepath, warning))
66 66 except IOError, inst:
67 if not skipwarning:
68 warn(_("skipping unreadable ignore file '%s': %s\n") %
69 (filepath, inst.strerror))
67 warn(_("skipping unreadable ignore file '%s': %s\n") %
68 (filepath, inst.strerror))
70 69 return pats
71 70
72 71 def readpats(root, files, warn):
73 72 '''return a dict mapping ignore-file-name to list-of-patterns'''
74 73
75 74 pats = {}
76 75 for f in files:
77 76 if f in pats:
78 77 continue
79 skipwarning = f == files[0]
80 pats[f] = readignorefile(f, warn, skipwarning=skipwarning)
78 pats[f] = readignorefile(f, warn)
81 79
82 80 return [(f, pats[f]) for f in files if f in pats]
83 81
84 82 def ignore(root, files, warn):
85 83 '''return matcher covering patterns in 'files'.
86 84
87 85 the files parsed for patterns include:
88 86 .hgignore in the repository root
89 87 any additional files specified in the [ui] section of ~/.hgrc
90 88
91 89 trailing white space is dropped.
92 90 the escape character is backslash.
93 91 comments start with #.
94 92 empty lines are skipped.
95 93
96 94 lines can be of the following formats:
97 95
98 96 syntax: regexp # defaults following lines to non-rooted regexps
99 97 syntax: glob # defaults following lines to non-rooted globs
100 98 re:pattern # non-rooted regular expression
101 99 glob:pattern # non-rooted glob
102 100 pattern # pattern of the current default type'''
103 101
104 102 pats = readpats(root, files, warn)
105 103
106 104 allpats = []
107 105 for f, patlist in pats:
108 106 allpats.extend(patlist)
109 107 if not allpats:
110 108 return util.never
111 109
112 110 try:
113 111 ignorefunc = match.match(root, '', [], allpats)
114 112 except util.Abort:
115 113 # Re-raise an exception where the src is the right file
116 114 for f, patlist in pats:
117 115 try:
118 116 match.match(root, '', [], patlist)
119 117 except util.Abort, inst:
120 118 raise util.Abort('%s: %s' % (f, inst[0]))
121 119
122 120 return ignorefunc
General Comments 0
You need to be logged in to leave comments. Login now