comparison mercurial/match.py @ 32458:a04bc55201c3

match: extract base class for matchers We will soon start splitting up the current matcher class into more specialized classes, so we'll want a base class for all the things that don't vary much between different matchers.
author Martin von Zweigbergk <martinvonz@google.com>
date Wed, 17 May 2017 23:45:13 -0700
parents 57d6c0c74b1b
children 9f781f43f2ce
comparison
equal deleted inserted replaced
32457:2def402bd16d 32458:a04bc55201c3
200 continue 200 continue
201 # else: re or relre - which cannot be normalized 201 # else: re or relre - which cannot be normalized
202 kindpats.append((kind, pat, '')) 202 kindpats.append((kind, pat, ''))
203 return kindpats 203 return kindpats
204 204
205 class matcher(object): 205 class basematcher(object):
206
207 def __init__(self, root, cwd, badfn=None):
208 self._root = root
209 self._cwd = cwd
210 if badfn is not None:
211 self.bad = badfn
212 self._files = [] # exact files and roots of patterns
213 self.matchfn = lambda f: False
214
215 def __call__(self, fn):
216 return self.matchfn(fn)
217 def __iter__(self):
218 for f in self._files:
219 yield f
220 # Callbacks related to how the matcher is used by dirstate.walk.
221 # Subscribers to these events must monkeypatch the matcher object.
222 def bad(self, f, msg):
223 '''Callback from dirstate.walk for each explicit file that can't be
224 found/accessed, with an error message.'''
225 pass
226
227 # If an explicitdir is set, it will be called when an explicitly listed
228 # directory is visited.
229 explicitdir = None
230
231 # If an traversedir is set, it will be called when a directory discovered
232 # by recursive traversal is visited.
233 traversedir = None
234
235 def abs(self, f):
236 '''Convert a repo path back to path that is relative to the root of the
237 matcher.'''
238 return f
239
240 def rel(self, f):
241 '''Convert repo path back to path that is relative to cwd of matcher.'''
242 return util.pathto(self._root, self._cwd, f)
243
244 def uipath(self, f):
245 '''Convert repo path to a display path. If patterns or -I/-X were used
246 to create this matcher, the display path will be relative to cwd.
247 Otherwise it is relative to the root of the repo.'''
248 return self.rel(f)
249
250 def files(self):
251 '''Explicitly listed files or patterns or roots:
252 if no patterns or .always(): empty list,
253 if exact: list exact files,
254 if not .anypats(): list all files and dirs,
255 else: optimal roots'''
256 return self._files
257
258 @propertycache
259 def _fileset(self):
260 return set(self._files)
261
262 def exact(self, f):
263 '''Returns True if f is in .files().'''
264 return f in self._fileset
265
266 def visitdir(self, dir):
267 '''Decides whether a directory should be visited based on whether it
268 has potential matches in it or one of its subdirectories. This is
269 based on the match's primary, included, and excluded patterns.
270
271 Returns the string 'all' if the given directory and all subdirectories
272 should be visited. Otherwise returns True or False indicating whether
273 the given directory should be visited.
274
275 This function's behavior is undefined if it has returned False for
276 one of the dir's parent directories.
277 '''
278 return False
279
280 def anypats(self):
281 '''Matcher uses patterns or include/exclude.'''
282 return False
283
284 def always(self):
285 '''Matcher will match everything and .files() will be empty
286 - optimization might be possible and necessary.'''
287 return False
288
289 def isexact(self):
290 return False
291
292 def prefix(self):
293 return not self.always() and not self.isexact() and not self.anypats()
294
295 class matcher(basematcher):
206 296
207 def __init__(self, root, cwd, normalize, patterns, include=None, 297 def __init__(self, root, cwd, normalize, patterns, include=None,
208 exclude=None, default='glob', exact=False, auditor=None, 298 exclude=None, default='glob', exact=False, auditor=None,
209 ctx=None, listsubrepos=False, warn=None, badfn=None): 299 ctx=None, listsubrepos=False, warn=None, badfn=None):
300 super(matcher, self).__init__(root, cwd, badfn)
210 if include is None: 301 if include is None:
211 include = [] 302 include = []
212 if exclude is None: 303 if exclude is None:
213 exclude = [] 304 exclude = []
214 305
215 self._root = root
216 self._cwd = cwd
217 self._files = [] # exact files and roots of patterns
218 self._anypats = bool(include or exclude) 306 self._anypats = bool(include or exclude)
219 self._always = False 307 self._always = False
220 self._pathrestricted = bool(include or exclude or patterns) 308 self._pathrestricted = bool(include or exclude or patterns)
221 self.patternspat = None 309 self.patternspat = None
222 self.includepat = None 310 self.includepat = None
225 # roots are directories which are recursively included/excluded. 313 # roots are directories which are recursively included/excluded.
226 self._includeroots = set() 314 self._includeroots = set()
227 self._excluderoots = set() 315 self._excluderoots = set()
228 # dirs are directories which are non-recursively included. 316 # dirs are directories which are non-recursively included.
229 self._includedirs = set() 317 self._includedirs = set()
230
231 if badfn is not None:
232 self.bad = badfn
233 318
234 matchfns = [] 319 matchfns = []
235 if include: 320 if include:
236 kindpats = normalize(include, 'glob', root, cwd, auditor, warn) 321 kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
237 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)', 322 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
279 return False 364 return False
280 return True 365 return True
281 366
282 self.matchfn = m 367 self.matchfn = m
283 368
284 def __call__(self, fn):
285 return self.matchfn(fn)
286 def __iter__(self):
287 for f in self._files:
288 yield f
289
290 # Callbacks related to how the matcher is used by dirstate.walk.
291 # Subscribers to these events must monkeypatch the matcher object.
292 def bad(self, f, msg):
293 '''Callback from dirstate.walk for each explicit file that can't be
294 found/accessed, with an error message.'''
295 pass
296
297 # If an explicitdir is set, it will be called when an explicitly listed
298 # directory is visited.
299 explicitdir = None
300
301 # If an traversedir is set, it will be called when a directory discovered
302 # by recursive traversal is visited.
303 traversedir = None
304
305 def abs(self, f):
306 '''Convert a repo path back to path that is relative to the root of the
307 matcher.'''
308 return f
309
310 def rel(self, f):
311 '''Convert repo path back to path that is relative to cwd of matcher.'''
312 return util.pathto(self._root, self._cwd, f)
313
314 def uipath(self, f): 369 def uipath(self, f):
315 '''Convert repo path to a display path. If patterns or -I/-X were used
316 to create this matcher, the display path will be relative to cwd.
317 Otherwise it is relative to the root of the repo.'''
318 return (self._pathrestricted and self.rel(f)) or self.abs(f) 370 return (self._pathrestricted and self.rel(f)) or self.abs(f)
319
320 def files(self):
321 '''Explicitly listed files or patterns or roots:
322 if no patterns or .always(): empty list,
323 if exact: list exact files,
324 if not .anypats(): list all files and dirs,
325 else: optimal roots'''
326 return self._files
327
328 @propertycache
329 def _fileset(self):
330 return set(self._files)
331 371
332 @propertycache 372 @propertycache
333 def _dirs(self): 373 def _dirs(self):
334 return set(util.dirs(self._fileset)) | {'.'} 374 return set(util.dirs(self._fileset)) | {'.'}
335 375
336 def visitdir(self, dir): 376 def visitdir(self, dir):
337 '''Decides whether a directory should be visited based on whether it
338 has potential matches in it or one of its subdirectories. This is
339 based on the match's primary, included, and excluded patterns.
340
341 Returns the string 'all' if the given directory and all subdirectories
342 should be visited. Otherwise returns True or False indicating whether
343 the given directory should be visited.
344
345 This function's behavior is undefined if it has returned False for
346 one of the dir's parent directories.
347 '''
348 if self.prefix() and dir in self._fileset: 377 if self.prefix() and dir in self._fileset:
349 return 'all' 378 return 'all'
350 if dir in self._excluderoots: 379 if dir in self._excluderoots:
351 return False 380 return False
352 if ((self._includeroots or self._includedirs) and 381 if ((self._includeroots or self._includedirs) and
361 dir in self._fileset or 390 dir in self._fileset or
362 dir in self._dirs or 391 dir in self._dirs or
363 any(parentdir in self._fileset 392 any(parentdir in self._fileset
364 for parentdir in util.finddirs(dir))) 393 for parentdir in util.finddirs(dir)))
365 394
366 def exact(self, f):
367 '''Returns True if f is in .files().'''
368 return f in self._fileset
369
370 def anypats(self): 395 def anypats(self):
371 '''Matcher uses patterns or include/exclude.'''
372 return self._anypats 396 return self._anypats
373 397
374 def always(self): 398 def always(self):
375 '''Matcher will match everything and .files() will be empty
376 - optimization might be possible and necessary.'''
377 return self._always 399 return self._always
378 400
379 def isexact(self): 401 def isexact(self):
380 return self.matchfn == self.exact 402 return self.matchfn == self.exact
381
382 def prefix(self):
383 return not self.always() and not self.isexact() and not self.anypats()
384 403
385 def __repr__(self): 404 def __repr__(self):
386 return ('<matcher files=%r, patterns=%r, includes=%r, excludes=%r>' % 405 return ('<matcher files=%r, patterns=%r, includes=%r, excludes=%r>' %
387 (self._files, self.patternspat, self.includepat, 406 (self._files, self.patternspat, self.includepat,
388 self.excludepat)) 407 self.excludepat))