##// END OF EJS Templates
match: fix a caseonly rename + explicit path commit on icasefs (issue4768)...
Matt Harbison -
r26000:9ac4e81b stable
parent child Browse files
Show More
@@ -1,672 +1,679 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 copy, os, 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 _expandsubinclude(kindpats, root):
46 46 '''Returns the list of subinclude matchers and the kindpats without the
47 47 subincludes in it.'''
48 48 relmatchers = []
49 49 other = []
50 50
51 51 for kind, pat, source in kindpats:
52 52 if kind == 'subinclude':
53 53 sourceroot = pathutil.dirname(util.normpath(source))
54 54 pat = util.pconvert(pat)
55 55 path = pathutil.join(sourceroot, pat)
56 56
57 57 newroot = pathutil.dirname(path)
58 58 relmatcher = match(newroot, '', [], ['include:%s' % path])
59 59
60 60 prefix = pathutil.canonpath(root, root, newroot)
61 61 if prefix:
62 62 prefix += '/'
63 63 relmatchers.append((prefix, relmatcher))
64 64 else:
65 65 other.append((kind, pat, source))
66 66
67 67 return relmatchers, other
68 68
69 69 def _kindpatsalwaysmatch(kindpats):
70 70 """"Checks whether the kindspats match everything, as e.g.
71 71 'relpath:.' does.
72 72 """
73 73 for kind, pat, source in kindpats:
74 74 if pat != '' or kind not in ['relpath', 'glob']:
75 75 return False
76 76 return True
77 77
78 78 class match(object):
79 79 def __init__(self, root, cwd, patterns, include=[], exclude=[],
80 80 default='glob', exact=False, auditor=None, ctx=None,
81 81 listsubrepos=False, warn=None, badfn=None):
82 82 """build an object to match a set of file patterns
83 83
84 84 arguments:
85 85 root - the canonical root of the tree you're matching against
86 86 cwd - the current working directory, if relevant
87 87 patterns - patterns to find
88 88 include - patterns to include (unless they are excluded)
89 89 exclude - patterns to exclude (even if they are included)
90 90 default - if a pattern in patterns has no explicit type, assume this one
91 91 exact - patterns are actually filenames (include/exclude still apply)
92 92 warn - optional function used for printing warnings
93 93 badfn - optional bad() callback for this matcher instead of the default
94 94
95 95 a pattern is one of:
96 96 'glob:<glob>' - a glob relative to cwd
97 97 're:<regexp>' - a regular expression
98 98 'path:<path>' - a path relative to repository root
99 99 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
100 100 'relpath:<path>' - a path relative to cwd
101 101 'relre:<regexp>' - a regexp that needn't match the start of a name
102 102 'set:<fileset>' - a fileset expression
103 103 'include:<path>' - a file of patterns to read and include
104 104 'subinclude:<path>' - a file of patterns to match against files under
105 105 the same directory
106 106 '<something>' - a pattern of the specified default type
107 107 """
108 108
109 109 self._root = root
110 110 self._cwd = cwd
111 111 self._files = [] # exact files and roots of patterns
112 112 self._anypats = bool(include or exclude)
113 113 self._always = False
114 114 self._pathrestricted = bool(include or exclude or patterns)
115 115 self._warn = warn
116 116 self._includeroots = set()
117 117 self._includedirs = set(['.'])
118 118 self._excluderoots = set()
119 119
120 120 if badfn is not None:
121 121 self.bad = badfn
122 122
123 123 matchfns = []
124 124 if include:
125 125 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
126 126 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
127 127 listsubrepos, root)
128 128 self._includeroots.update(_roots(kindpats))
129 129 self._includedirs.update(util.dirs(self._includeroots))
130 130 matchfns.append(im)
131 131 if exclude:
132 132 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
133 133 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
134 134 listsubrepos, root)
135 135 if not _anypats(kindpats):
136 136 self._excluderoots.update(_roots(kindpats))
137 137 matchfns.append(lambda f: not em(f))
138 138 if exact:
139 139 if isinstance(patterns, list):
140 140 self._files = patterns
141 141 else:
142 142 self._files = list(patterns)
143 143 matchfns.append(self.exact)
144 144 elif patterns:
145 145 kindpats = self._normalize(patterns, default, root, cwd, auditor)
146 146 if not _kindpatsalwaysmatch(kindpats):
147 147 self._files = _roots(kindpats)
148 148 self._anypats = self._anypats or _anypats(kindpats)
149 149 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
150 150 listsubrepos, root)
151 151 matchfns.append(pm)
152 152
153 153 if not matchfns:
154 154 m = util.always
155 155 self._always = True
156 156 elif len(matchfns) == 1:
157 157 m = matchfns[0]
158 158 else:
159 159 def m(f):
160 160 for matchfn in matchfns:
161 161 if not matchfn(f):
162 162 return False
163 163 return True
164 164
165 165 self.matchfn = m
166 166 self._fileroots = set(self._files)
167 167
168 168 def __call__(self, fn):
169 169 return self.matchfn(fn)
170 170 def __iter__(self):
171 171 for f in self._files:
172 172 yield f
173 173
174 174 # Callbacks related to how the matcher is used by dirstate.walk.
175 175 # Subscribers to these events must monkeypatch the matcher object.
176 176 def bad(self, f, msg):
177 177 '''Callback from dirstate.walk for each explicit file that can't be
178 178 found/accessed, with an error message.'''
179 179 pass
180 180
181 181 # If an explicitdir is set, it will be called when an explicitly listed
182 182 # directory is visited.
183 183 explicitdir = None
184 184
185 185 # If an traversedir is set, it will be called when a directory discovered
186 186 # by recursive traversal is visited.
187 187 traversedir = None
188 188
189 189 def abs(self, f):
190 190 '''Convert a repo path back to path that is relative to the root of the
191 191 matcher.'''
192 192 return f
193 193
194 194 def rel(self, f):
195 195 '''Convert repo path back to path that is relative to cwd of matcher.'''
196 196 return util.pathto(self._root, self._cwd, f)
197 197
198 198 def uipath(self, f):
199 199 '''Convert repo path to a display path. If patterns or -I/-X were used
200 200 to create this matcher, the display path will be relative to cwd.
201 201 Otherwise it is relative to the root of the repo.'''
202 202 return (self._pathrestricted and self.rel(f)) or self.abs(f)
203 203
204 204 def files(self):
205 205 '''Explicitly listed files or patterns or roots:
206 206 if no patterns or .always(): empty list,
207 207 if exact: list exact files,
208 208 if not .anypats(): list all files and dirs,
209 209 else: optimal roots'''
210 210 return self._files
211 211
212 212 @propertycache
213 213 def _dirs(self):
214 214 return set(util.dirs(self._fileroots)) | set(['.'])
215 215
216 216 def visitdir(self, dir):
217 217 '''Decides whether a directory should be visited based on whether it
218 218 has potential matches in it or one of its subdirectories. This is
219 219 based on the match's primary, included, and excluded patterns.
220 220
221 221 This function's behavior is undefined if it has returned False for
222 222 one of the dir's parent directories.
223 223 '''
224 224 if dir in self._excluderoots:
225 225 return False
226 226 if (self._includeroots and
227 227 '.' not in self._includeroots and
228 228 dir not in self._includeroots and
229 229 dir not in self._includedirs and
230 230 not any(parent in self._includeroots
231 231 for parent in util.finddirs(dir))):
232 232 return False
233 233 return (not self._fileroots or
234 234 '.' in self._fileroots or
235 235 dir in self._fileroots or
236 236 dir in self._dirs or
237 237 any(parentdir in self._fileroots
238 238 for parentdir in util.finddirs(dir)))
239 239
240 240 def exact(self, f):
241 241 '''Returns True if f is in .files().'''
242 242 return f in self._fileroots
243 243
244 244 def anypats(self):
245 245 '''Matcher uses patterns or include/exclude.'''
246 246 return self._anypats
247 247
248 248 def always(self):
249 249 '''Matcher will match everything and .files() will be empty
250 250 - optimization might be possible and necessary.'''
251 251 return self._always
252 252
253 253 def ispartial(self):
254 254 '''True if the matcher won't always match.
255 255
256 256 Although it's just the inverse of _always in this implementation,
257 257 an extenion such as narrowhg might make it return something
258 258 slightly different.'''
259 259 return not self._always
260 260
261 261 def isexact(self):
262 262 return self.matchfn == self.exact
263 263
264 264 def prefix(self):
265 265 return not self.always() and not self.isexact() and not self.anypats()
266 266
267 267 def _normalize(self, patterns, default, root, cwd, auditor):
268 268 '''Convert 'kind:pat' from the patterns list to tuples with kind and
269 269 normalized and rooted patterns and with listfiles expanded.'''
270 270 kindpats = []
271 271 for kind, pat in [_patsplit(p, default) for p in patterns]:
272 272 if kind in ('glob', 'relpath'):
273 273 pat = pathutil.canonpath(root, cwd, pat, auditor)
274 274 elif kind in ('relglob', 'path'):
275 275 pat = util.normpath(pat)
276 276 elif kind in ('listfile', 'listfile0'):
277 277 try:
278 278 files = util.readfile(pat)
279 279 if kind == 'listfile0':
280 280 files = files.split('\0')
281 281 else:
282 282 files = files.splitlines()
283 283 files = [f for f in files if f]
284 284 except EnvironmentError:
285 285 raise util.Abort(_("unable to read file list (%s)") % pat)
286 286 for k, p, source in self._normalize(files, default, root, cwd,
287 287 auditor):
288 288 kindpats.append((k, p, pat))
289 289 continue
290 290 elif kind == 'include':
291 291 try:
292 292 fullpath = os.path.join(root, util.localpath(pat))
293 293 includepats = readpatternfile(fullpath, self._warn)
294 294 for k, p, source in self._normalize(includepats, default,
295 295 root, cwd, auditor):
296 296 kindpats.append((k, p, source or pat))
297 297 except util.Abort as inst:
298 298 raise util.Abort('%s: %s' % (pat, inst[0]))
299 299 except IOError as inst:
300 300 if self._warn:
301 301 self._warn(_("skipping unreadable pattern file "
302 302 "'%s': %s\n") % (pat, inst.strerror))
303 303 continue
304 304 # else: re or relre - which cannot be normalized
305 305 kindpats.append((kind, pat, ''))
306 306 return kindpats
307 307
308 308 def exact(root, cwd, files, badfn=None):
309 309 return match(root, cwd, files, exact=True, badfn=badfn)
310 310
311 311 def always(root, cwd):
312 312 return match(root, cwd, [])
313 313
314 314 def badmatch(match, badfn):
315 315 """Make a copy of the given matcher, replacing its bad method with the given
316 316 one.
317 317 """
318 318 m = copy.copy(match)
319 319 m.bad = badfn
320 320 return m
321 321
322 322 class narrowmatcher(match):
323 323 """Adapt a matcher to work on a subdirectory only.
324 324
325 325 The paths are remapped to remove/insert the path as needed:
326 326
327 327 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
328 328 >>> m2 = narrowmatcher('sub', m1)
329 329 >>> bool(m2('a.txt'))
330 330 False
331 331 >>> bool(m2('b.txt'))
332 332 True
333 333 >>> bool(m2.matchfn('a.txt'))
334 334 False
335 335 >>> bool(m2.matchfn('b.txt'))
336 336 True
337 337 >>> m2.files()
338 338 ['b.txt']
339 339 >>> m2.exact('b.txt')
340 340 True
341 341 >>> util.pconvert(m2.rel('b.txt'))
342 342 'sub/b.txt'
343 343 >>> def bad(f, msg):
344 344 ... print "%s: %s" % (f, msg)
345 345 >>> m1.bad = bad
346 346 >>> m2.bad('x.txt', 'No such file')
347 347 sub/x.txt: No such file
348 348 >>> m2.abs('c.txt')
349 349 'sub/c.txt'
350 350 """
351 351
352 352 def __init__(self, path, matcher):
353 353 self._root = matcher._root
354 354 self._cwd = matcher._cwd
355 355 self._path = path
356 356 self._matcher = matcher
357 357 self._always = matcher._always
358 358 self._pathrestricted = matcher._pathrestricted
359 359
360 360 self._files = [f[len(path) + 1:] for f in matcher._files
361 361 if f.startswith(path + "/")]
362 362
363 363 # If the parent repo had a path to this subrepo and no patterns are
364 364 # specified, this submatcher always matches.
365 365 if not self._always and not matcher._anypats:
366 366 self._always = any(f == path for f in matcher._files)
367 367
368 368 self._anypats = matcher._anypats
369 369 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
370 370 self._fileroots = set(self._files)
371 371
372 372 def abs(self, f):
373 373 return self._matcher.abs(self._path + "/" + f)
374 374
375 375 def bad(self, f, msg):
376 376 self._matcher.bad(self._path + "/" + f, msg)
377 377
378 378 def rel(self, f):
379 379 return self._matcher.rel(self._path + "/" + f)
380 380
381 381 class icasefsmatcher(match):
382 382 """A matcher for wdir on case insensitive filesystems, which normalizes the
383 383 given patterns to the case in the filesystem.
384 384 """
385 385
386 386 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
387 387 ctx, listsubrepos=False, badfn=None):
388 388 init = super(icasefsmatcher, self).__init__
389 self._dsnormalize = ctx.repo().dirstate.normalize
389 self._dirstate = ctx.repo().dirstate
390 self._dsnormalize = self._dirstate.normalize
390 391
391 392 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
392 393 ctx=ctx, listsubrepos=listsubrepos, badfn=badfn)
393 394
394 395 # m.exact(file) must be based off of the actual user input, otherwise
395 396 # inexact case matches are treated as exact, and not noted without -v.
396 397 if self._files:
397 398 self._fileroots = set(_roots(self._kp))
398 399
399 400 def _normalize(self, patterns, default, root, cwd, auditor):
400 401 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
401 402 root, cwd, auditor)
402 403 kindpats = []
403 404 for kind, pats, source in self._kp:
404 405 if kind not in ('re', 'relre'): # regex can't be normalized
406 p = pats
405 407 pats = self._dsnormalize(pats)
408
409 # Preserve the original to handle a case only rename.
410 if p != pats and p in self._dirstate:
411 kindpats.append((kind, p, source))
412
406 413 kindpats.append((kind, pats, source))
407 414 return kindpats
408 415
409 416 def patkind(pattern, default=None):
410 417 '''If pattern is 'kind:pat' with a known kind, return kind.'''
411 418 return _patsplit(pattern, default)[0]
412 419
413 420 def _patsplit(pattern, default):
414 421 """Split a string into the optional pattern kind prefix and the actual
415 422 pattern."""
416 423 if ':' in pattern:
417 424 kind, pat = pattern.split(':', 1)
418 425 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
419 426 'listfile', 'listfile0', 'set', 'include', 'subinclude'):
420 427 return kind, pat
421 428 return default, pattern
422 429
423 430 def _globre(pat):
424 431 r'''Convert an extended glob string to a regexp string.
425 432
426 433 >>> print _globre(r'?')
427 434 .
428 435 >>> print _globre(r'*')
429 436 [^/]*
430 437 >>> print _globre(r'**')
431 438 .*
432 439 >>> print _globre(r'**/a')
433 440 (?:.*/)?a
434 441 >>> print _globre(r'a/**/b')
435 442 a\/(?:.*/)?b
436 443 >>> print _globre(r'[a*?!^][^b][!c]')
437 444 [a*?!^][\^b][^c]
438 445 >>> print _globre(r'{a,b}')
439 446 (?:a|b)
440 447 >>> print _globre(r'.\*\?')
441 448 \.\*\?
442 449 '''
443 450 i, n = 0, len(pat)
444 451 res = ''
445 452 group = 0
446 453 escape = util.re.escape
447 454 def peek():
448 455 return i < n and pat[i]
449 456 while i < n:
450 457 c = pat[i]
451 458 i += 1
452 459 if c not in '*?[{},\\':
453 460 res += escape(c)
454 461 elif c == '*':
455 462 if peek() == '*':
456 463 i += 1
457 464 if peek() == '/':
458 465 i += 1
459 466 res += '(?:.*/)?'
460 467 else:
461 468 res += '.*'
462 469 else:
463 470 res += '[^/]*'
464 471 elif c == '?':
465 472 res += '.'
466 473 elif c == '[':
467 474 j = i
468 475 if j < n and pat[j] in '!]':
469 476 j += 1
470 477 while j < n and pat[j] != ']':
471 478 j += 1
472 479 if j >= n:
473 480 res += '\\['
474 481 else:
475 482 stuff = pat[i:j].replace('\\','\\\\')
476 483 i = j + 1
477 484 if stuff[0] == '!':
478 485 stuff = '^' + stuff[1:]
479 486 elif stuff[0] == '^':
480 487 stuff = '\\' + stuff
481 488 res = '%s[%s]' % (res, stuff)
482 489 elif c == '{':
483 490 group += 1
484 491 res += '(?:'
485 492 elif c == '}' and group:
486 493 res += ')'
487 494 group -= 1
488 495 elif c == ',' and group:
489 496 res += '|'
490 497 elif c == '\\':
491 498 p = peek()
492 499 if p:
493 500 i += 1
494 501 res += escape(p)
495 502 else:
496 503 res += escape(c)
497 504 else:
498 505 res += escape(c)
499 506 return res
500 507
501 508 def _regex(kind, pat, globsuffix):
502 509 '''Convert a (normalized) pattern of any kind into a regular expression.
503 510 globsuffix is appended to the regexp of globs.'''
504 511 if not pat:
505 512 return ''
506 513 if kind == 're':
507 514 return pat
508 515 if kind == 'path':
509 516 if pat == '.':
510 517 return ''
511 518 return '^' + util.re.escape(pat) + '(?:/|$)'
512 519 if kind == 'relglob':
513 520 return '(?:|.*/)' + _globre(pat) + globsuffix
514 521 if kind == 'relpath':
515 522 return util.re.escape(pat) + '(?:/|$)'
516 523 if kind == 'relre':
517 524 if pat.startswith('^'):
518 525 return pat
519 526 return '.*' + pat
520 527 return _globre(pat) + globsuffix
521 528
522 529 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
523 530 '''Return regexp string and a matcher function for kindpats.
524 531 globsuffix is appended to the regexp of globs.'''
525 532 matchfuncs = []
526 533
527 534 subincludes, kindpats = _expandsubinclude(kindpats, root)
528 535 if subincludes:
529 536 def matchsubinclude(f):
530 537 for prefix, mf in subincludes:
531 538 if f.startswith(prefix) and mf(f[len(prefix):]):
532 539 return True
533 540 return False
534 541 matchfuncs.append(matchsubinclude)
535 542
536 543 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
537 544 if fset:
538 545 matchfuncs.append(fset.__contains__)
539 546
540 547 regex = ''
541 548 if kindpats:
542 549 regex, mf = _buildregexmatch(kindpats, globsuffix)
543 550 matchfuncs.append(mf)
544 551
545 552 if len(matchfuncs) == 1:
546 553 return regex, matchfuncs[0]
547 554 else:
548 555 return regex, lambda f: any(mf(f) for mf in matchfuncs)
549 556
550 557 def _buildregexmatch(kindpats, globsuffix):
551 558 """Build a match function from a list of kinds and kindpats,
552 559 return regexp string and a matcher function."""
553 560 try:
554 561 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
555 562 for (k, p, s) in kindpats])
556 563 if len(regex) > 20000:
557 564 raise OverflowError
558 565 return regex, _rematcher(regex)
559 566 except OverflowError:
560 567 # We're using a Python with a tiny regex engine and we
561 568 # made it explode, so we'll divide the pattern list in two
562 569 # until it works
563 570 l = len(kindpats)
564 571 if l < 2:
565 572 raise
566 573 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
567 574 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
568 575 return regex, lambda s: a(s) or b(s)
569 576 except re.error:
570 577 for k, p, s in kindpats:
571 578 try:
572 579 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
573 580 except re.error:
574 581 if s:
575 582 raise util.Abort(_("%s: invalid pattern (%s): %s") %
576 583 (s, k, p))
577 584 else:
578 585 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
579 586 raise util.Abort(_("invalid pattern"))
580 587
581 588 def _roots(kindpats):
582 589 '''return roots and exact explicitly listed files from patterns
583 590
584 591 >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')])
585 592 ['g', 'g', '.']
586 593 >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
587 594 ['r', 'p/p', '.']
588 595 >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
589 596 ['.', '.', '.']
590 597 '''
591 598 r = []
592 599 for kind, pat, source in kindpats:
593 600 if kind == 'glob': # find the non-glob prefix
594 601 root = []
595 602 for p in pat.split('/'):
596 603 if '[' in p or '{' in p or '*' in p or '?' in p:
597 604 break
598 605 root.append(p)
599 606 r.append('/'.join(root) or '.')
600 607 elif kind in ('relpath', 'path'):
601 608 r.append(pat or '.')
602 609 else: # relglob, re, relre
603 610 r.append('.')
604 611 return r
605 612
606 613 def _anypats(kindpats):
607 614 for kind, pat, source in kindpats:
608 615 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
609 616 return True
610 617
611 618 _commentre = None
612 619
613 620 def readpatternfile(filepath, warn):
614 621 '''parse a pattern file, returning a list of
615 622 patterns. These patterns should be given to compile()
616 623 to be validated and converted into a match function.
617 624
618 625 trailing white space is dropped.
619 626 the escape character is backslash.
620 627 comments start with #.
621 628 empty lines are skipped.
622 629
623 630 lines can be of the following formats:
624 631
625 632 syntax: regexp # defaults following lines to non-rooted regexps
626 633 syntax: glob # defaults following lines to non-rooted globs
627 634 re:pattern # non-rooted regular expression
628 635 glob:pattern # non-rooted glob
629 636 pattern # pattern of the current default type'''
630 637
631 638 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
632 639 'include': 'include', 'subinclude': 'subinclude'}
633 640 syntax = 'relre:'
634 641 patterns = []
635 642
636 643 fp = open(filepath)
637 644 for line in fp:
638 645 if "#" in line:
639 646 global _commentre
640 647 if not _commentre:
641 648 _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
642 649 # remove comments prefixed by an even number of escapes
643 650 line = _commentre.sub(r'\1', line)
644 651 # fixup properly escaped comments that survived the above
645 652 line = line.replace("\\#", "#")
646 653 line = line.rstrip()
647 654 if not line:
648 655 continue
649 656
650 657 if line.startswith('syntax:'):
651 658 s = line[7:].strip()
652 659 try:
653 660 syntax = syntaxes[s]
654 661 except KeyError:
655 662 if warn:
656 663 warn(_("%s: ignoring invalid syntax '%s'\n") %
657 664 (filepath, s))
658 665 continue
659 666
660 667 linesyntax = syntax
661 668 for s, rels in syntaxes.iteritems():
662 669 if line.startswith(rels):
663 670 linesyntax = rels
664 671 line = line[len(rels):]
665 672 break
666 673 elif line.startswith(s+':'):
667 674 linesyntax = rels
668 675 line = line[len(s) + 1:]
669 676 break
670 677 patterns.append(linesyntax + line)
671 678 fp.close()
672 679 return patterns
@@ -1,241 +1,249 b''
1 1 $ hg init a
2 2 $ cd a
3 3 $ echo a > a
4 4 $ hg add -n
5 5 adding a
6 6 $ hg st
7 7 ? a
8 8 $ hg add
9 9 adding a
10 10 $ hg st
11 11 A a
12 12 $ hg forget a
13 13 $ hg add
14 14 adding a
15 15 $ hg st
16 16 A a
17 17
18 18 $ echo b > b
19 19 $ hg add -n b
20 20 $ hg st
21 21 A a
22 22 ? b
23 23 $ hg add b
24 24 $ hg st
25 25 A a
26 26 A b
27 27
28 28 should fail
29 29
30 30 $ hg add b
31 31 b already tracked!
32 32 $ hg st
33 33 A a
34 34 A b
35 35
36 36 #if no-windows
37 37 $ echo foo > con.xml
38 38 $ hg --config ui.portablefilenames=jump add con.xml
39 39 abort: ui.portablefilenames value is invalid ('jump')
40 40 [255]
41 41 $ hg --config ui.portablefilenames=abort add con.xml
42 42 abort: filename contains 'con', which is reserved on Windows: 'con.xml'
43 43 [255]
44 44 $ hg st
45 45 A a
46 46 A b
47 47 ? con.xml
48 48 $ hg add con.xml
49 49 warning: filename contains 'con', which is reserved on Windows: 'con.xml'
50 50 $ hg st
51 51 A a
52 52 A b
53 53 A con.xml
54 54 $ hg forget con.xml
55 55 $ rm con.xml
56 56 #endif
57 57
58 58 #if eol-in-paths
59 59 $ echo bla > 'hello:world'
60 60 $ hg --config ui.portablefilenames=abort add
61 61 adding hello:world
62 62 abort: filename contains ':', which is reserved on Windows: 'hello:world'
63 63 [255]
64 64 $ hg st
65 65 A a
66 66 A b
67 67 ? hello:world
68 68 $ hg --config ui.portablefilenames=ignore add
69 69 adding hello:world
70 70 $ hg st
71 71 A a
72 72 A b
73 73 A hello:world
74 74 #endif
75 75
76 76 $ hg ci -m 0 --traceback
77 77
78 78 $ hg log -r "heads(. or wdir() & file('**'))"
79 79 changeset: 0:* (glob)
80 80 tag: tip
81 81 user: test
82 82 date: Thu Jan 01 00:00:00 1970 +0000
83 83 summary: 0
84 84
85 85 should fail
86 86
87 87 $ hg add a
88 88 a already tracked!
89 89
90 90 $ echo aa > a
91 91 $ hg ci -m 1
92 92 $ hg up 0
93 93 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 94 $ echo aaa > a
95 95 $ hg ci -m 2
96 96 created new head
97 97
98 98 $ hg merge
99 99 merging a
100 100 warning: conflicts during merge.
101 101 merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
102 102 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
103 103 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
104 104 [1]
105 105 $ hg st
106 106 M a
107 107 ? a.orig
108 108
109 109 wdir doesn't cause a crash, and can be dynamically selected if dirty
110 110
111 111 $ hg log -r "heads(. or wdir() & file('**'))"
112 112 changeset: 2147483647:ffffffffffff
113 113 parent: 2:* (glob)
114 114 parent: 1:* (glob)
115 115 user: test
116 116 date: * (glob)
117 117
118 118 should fail
119 119
120 120 $ hg add a
121 121 a already tracked!
122 122 $ hg st
123 123 M a
124 124 ? a.orig
125 125 $ hg resolve -m a
126 126 (no more unresolved files)
127 127 $ hg ci -m merge
128 128
129 129 Issue683: peculiarity with hg revert of an removed then added file
130 130
131 131 $ hg forget a
132 132 $ hg add a
133 133 $ hg st
134 134 ? a.orig
135 135 $ hg rm a
136 136 $ hg st
137 137 R a
138 138 ? a.orig
139 139 $ echo a > a
140 140 $ hg add a
141 141 $ hg st
142 142 M a
143 143 ? a.orig
144 144
145 145 Forgotten file can be added back (as either clean or modified)
146 146
147 147 $ hg forget b
148 148 $ hg add b
149 149 $ hg st -A b
150 150 C b
151 151 $ hg forget b
152 152 $ echo modified > b
153 153 $ hg add b
154 154 $ hg st -A b
155 155 M b
156 156 $ hg revert -qC b
157 157
158 158 $ hg add c && echo "unexpected addition of missing file"
159 159 c: * (glob)
160 160 [1]
161 161 $ echo c > c
162 162 $ hg add d c && echo "unexpected addition of missing file"
163 163 d: * (glob)
164 164 [1]
165 165 $ hg st
166 166 M a
167 167 A c
168 168 ? a.orig
169 169 $ hg up -C
170 170 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 171
172 172 forget and get should have the right order: added but missing dir should be
173 173 forgotten before file with same name is added
174 174
175 175 $ echo file d > d
176 176 $ hg add d
177 177 $ hg ci -md
178 178 $ hg rm d
179 179 $ mkdir d
180 180 $ echo a > d/a
181 181 $ hg add d/a
182 182 $ rm -r d
183 183 $ hg up -C
184 184 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
185 185 $ cat d
186 186 file d
187 187
188 188 Test that adding a directory doesn't require case matching (issue4578)
189 189 #if icasefs
190 190 $ mkdir -p CapsDir1/CapsDir
191 191 $ echo abc > CapsDir1/CapsDir/AbC.txt
192 192 $ mkdir CapsDir1/CapsDir/SubDir
193 193 $ echo def > CapsDir1/CapsDir/SubDir/Def.txt
194 194
195 195 $ hg add capsdir1/capsdir
196 196 adding CapsDir1/CapsDir/AbC.txt (glob)
197 197 adding CapsDir1/CapsDir/SubDir/Def.txt (glob)
198 198
199 199 $ hg forget capsdir1/capsdir/abc.txt
200 200 removing CapsDir1/CapsDir/AbC.txt (glob)
201 201
202 202 $ hg forget capsdir1/capsdir
203 203 removing CapsDir1/CapsDir/SubDir/Def.txt (glob)
204 204
205 205 $ hg add capsdir1
206 206 adding CapsDir1/CapsDir/AbC.txt (glob)
207 207 adding CapsDir1/CapsDir/SubDir/Def.txt (glob)
208 208
209 209 $ hg ci -m "AbCDef" capsdir1/capsdir
210 210
211 211 $ hg status -A capsdir1/capsdir
212 212 C CapsDir1/CapsDir/AbC.txt
213 213 C CapsDir1/CapsDir/SubDir/Def.txt
214 214
215 215 $ hg files capsdir1/capsdir
216 216 CapsDir1/CapsDir/AbC.txt (glob)
217 217 CapsDir1/CapsDir/SubDir/Def.txt (glob)
218 218
219 219 $ echo xyz > CapsDir1/CapsDir/SubDir/Def.txt
220 220 $ hg ci -m xyz capsdir1/capsdir/subdir/def.txt
221 221
222 222 $ hg revert -r '.^' capsdir1/capsdir
223 223 reverting CapsDir1/CapsDir/SubDir/Def.txt (glob)
224 224
225 225 The conditional tests above mean the hash on the diff line differs on Windows
226 226 and OS X
227 227 $ hg diff capsdir1/capsdir
228 228 diff -r * CapsDir1/CapsDir/SubDir/Def.txt (glob)
229 229 --- a/CapsDir1/CapsDir/SubDir/Def.txt Thu Jan 01 00:00:00 1970 +0000
230 230 +++ b/CapsDir1/CapsDir/SubDir/Def.txt * +0000 (glob)
231 231 @@ -1,1 +1,1 @@
232 232 -xyz
233 233 +def
234 234
235 $ hg mv CapsDir1/CapsDir/abc.txt CapsDir1/CapsDir/ABC.txt
236 moving CapsDir1/CapsDir/AbC.txt to CapsDir1/CapsDir/ABC.txt (glob)
237 $ hg ci -m "case changing rename" CapsDir1/CapsDir/AbC.txt CapsDir1/CapsDir/ABC.txt
238
239 $ hg status -A capsdir1/capsdir
240 M CapsDir1/CapsDir/SubDir/Def.txt
241 C CapsDir1/CapsDir/ABC.txt
242
235 243 $ hg remove -f 'glob:**.txt' -X capsdir1/capsdir
236 244 $ hg remove -f 'glob:**.txt' -I capsdir1/capsdir
237 removing CapsDir1/CapsDir/AbC.txt (glob)
245 removing CapsDir1/CapsDir/ABC.txt (glob)
238 246 removing CapsDir1/CapsDir/SubDir/Def.txt (glob)
239 247 #endif
240 248
241 249 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now