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