##// END OF EJS Templates
match: have visitdir() consider includes and excludes...
Drew Gottlieb -
r25231:8545bd38 default
parent child Browse files
Show More
@@ -1,587 +1,610
1 # match.py - filename matching
1 # match.py - filename matching
2 #
2 #
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import re
8 import re
9 import util, pathutil
9 import util, pathutil
10 from i18n import _
10 from i18n import _
11
11
12 propertycache = util.propertycache
12 propertycache = util.propertycache
13
13
14 def _rematcher(regex):
14 def _rematcher(regex):
15 '''compile the regexp with the best available regexp engine and return a
15 '''compile the regexp with the best available regexp engine and return a
16 matcher function'''
16 matcher function'''
17 m = util.re.compile(regex)
17 m = util.re.compile(regex)
18 try:
18 try:
19 # slightly faster, provided by facebook's re2 bindings
19 # slightly faster, provided by facebook's re2 bindings
20 return m.test_match
20 return m.test_match
21 except AttributeError:
21 except AttributeError:
22 return m.match
22 return m.match
23
23
24 def _expandsets(kindpats, ctx, listsubrepos):
24 def _expandsets(kindpats, ctx, listsubrepos):
25 '''Returns the kindpats list with the 'set' patterns expanded.'''
25 '''Returns the kindpats list with the 'set' patterns expanded.'''
26 fset = set()
26 fset = set()
27 other = []
27 other = []
28
28
29 for kind, pat, source in kindpats:
29 for kind, pat, source in kindpats:
30 if kind == 'set':
30 if kind == 'set':
31 if not ctx:
31 if not ctx:
32 raise util.Abort("fileset expression with no context")
32 raise util.Abort("fileset expression with no context")
33 s = ctx.getfileset(pat)
33 s = ctx.getfileset(pat)
34 fset.update(s)
34 fset.update(s)
35
35
36 if listsubrepos:
36 if listsubrepos:
37 for subpath in ctx.substate:
37 for subpath in ctx.substate:
38 s = ctx.sub(subpath).getfileset(pat)
38 s = ctx.sub(subpath).getfileset(pat)
39 fset.update(subpath + '/' + f for f in s)
39 fset.update(subpath + '/' + f for f in s)
40
40
41 continue
41 continue
42 other.append((kind, pat, source))
42 other.append((kind, pat, source))
43 return fset, other
43 return fset, other
44
44
45 def _kindpatsalwaysmatch(kindpats):
45 def _kindpatsalwaysmatch(kindpats):
46 """"Checks whether the kindspats match everything, as e.g.
46 """"Checks whether the kindspats match everything, as e.g.
47 'relpath:.' does.
47 'relpath:.' does.
48 """
48 """
49 for kind, pat, source in kindpats:
49 for kind, pat, source in kindpats:
50 if pat != '' or kind not in ['relpath', 'glob']:
50 if pat != '' or kind not in ['relpath', 'glob']:
51 return False
51 return False
52 return True
52 return True
53
53
54 class match(object):
54 class match(object):
55 def __init__(self, root, cwd, patterns, include=[], exclude=[],
55 def __init__(self, root, cwd, patterns, include=[], exclude=[],
56 default='glob', exact=False, auditor=None, ctx=None,
56 default='glob', exact=False, auditor=None, ctx=None,
57 listsubrepos=False, warn=None):
57 listsubrepos=False, warn=None):
58 """build an object to match a set of file patterns
58 """build an object to match a set of file patterns
59
59
60 arguments:
60 arguments:
61 root - the canonical root of the tree you're matching against
61 root - the canonical root of the tree you're matching against
62 cwd - the current working directory, if relevant
62 cwd - the current working directory, if relevant
63 patterns - patterns to find
63 patterns - patterns to find
64 include - patterns to include (unless they are excluded)
64 include - patterns to include (unless they are excluded)
65 exclude - patterns to exclude (even if they are included)
65 exclude - patterns to exclude (even if they are included)
66 default - if a pattern in patterns has no explicit type, assume this one
66 default - if a pattern in patterns has no explicit type, assume this one
67 exact - patterns are actually filenames (include/exclude still apply)
67 exact - patterns are actually filenames (include/exclude still apply)
68 warn - optional function used for printing warnings
68 warn - optional function used for printing warnings
69
69
70 a pattern is one of:
70 a pattern is one of:
71 'glob:<glob>' - a glob relative to cwd
71 'glob:<glob>' - a glob relative to cwd
72 're:<regexp>' - a regular expression
72 're:<regexp>' - a regular expression
73 'path:<path>' - a path relative to repository root
73 'path:<path>' - a path relative to repository root
74 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
74 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
75 'relpath:<path>' - a path relative to cwd
75 'relpath:<path>' - a path relative to cwd
76 'relre:<regexp>' - a regexp that needn't match the start of a name
76 'relre:<regexp>' - a regexp that needn't match the start of a name
77 'set:<fileset>' - a fileset expression
77 'set:<fileset>' - a fileset expression
78 'include:<path>' - a file of patterns to read and include
78 'include:<path>' - a file of patterns to read and include
79 '<something>' - a pattern of the specified default type
79 '<something>' - a pattern of the specified default type
80 """
80 """
81
81
82 self._root = root
82 self._root = root
83 self._cwd = cwd
83 self._cwd = cwd
84 self._files = [] # exact files and roots of patterns
84 self._files = [] # exact files and roots of patterns
85 self._anypats = bool(include or exclude)
85 self._anypats = bool(include or exclude)
86 self._always = False
86 self._always = False
87 self._pathrestricted = bool(include or exclude or patterns)
87 self._pathrestricted = bool(include or exclude or patterns)
88 self._warn = warn
88 self._warn = warn
89 self._includeroots = set()
90 self._includedirs = set(['.'])
91 self._excluderoots = set()
89
92
90 matchfns = []
93 matchfns = []
91 if include:
94 if include:
92 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
95 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
93 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
96 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
94 listsubrepos)
97 listsubrepos)
98 self._includeroots.update(_roots(kindpats))
99 self._includeroots.discard('.')
100 self._includedirs.update(util.dirs(self._includeroots))
95 matchfns.append(im)
101 matchfns.append(im)
96 if exclude:
102 if exclude:
97 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
103 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
98 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
104 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
99 listsubrepos)
105 listsubrepos)
106 self._excluderoots.update(_roots(kindpats))
107 self._excluderoots.discard('.')
100 matchfns.append(lambda f: not em(f))
108 matchfns.append(lambda f: not em(f))
101 if exact:
109 if exact:
102 if isinstance(patterns, list):
110 if isinstance(patterns, list):
103 self._files = patterns
111 self._files = patterns
104 else:
112 else:
105 self._files = list(patterns)
113 self._files = list(patterns)
106 matchfns.append(self.exact)
114 matchfns.append(self.exact)
107 elif patterns:
115 elif patterns:
108 kindpats = self._normalize(patterns, default, root, cwd, auditor)
116 kindpats = self._normalize(patterns, default, root, cwd, auditor)
109 if not _kindpatsalwaysmatch(kindpats):
117 if not _kindpatsalwaysmatch(kindpats):
110 self._files = _roots(kindpats)
118 self._files = _roots(kindpats)
111 self._anypats = self._anypats or _anypats(kindpats)
119 self._anypats = self._anypats or _anypats(kindpats)
112 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
120 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
113 listsubrepos)
121 listsubrepos)
114 matchfns.append(pm)
122 matchfns.append(pm)
115
123
116 if not matchfns:
124 if not matchfns:
117 m = util.always
125 m = util.always
118 self._always = True
126 self._always = True
119 elif len(matchfns) == 1:
127 elif len(matchfns) == 1:
120 m = matchfns[0]
128 m = matchfns[0]
121 else:
129 else:
122 def m(f):
130 def m(f):
123 for matchfn in matchfns:
131 for matchfn in matchfns:
124 if not matchfn(f):
132 if not matchfn(f):
125 return False
133 return False
126 return True
134 return True
127
135
128 self.matchfn = m
136 self.matchfn = m
129 self._fileroots = set(self._files)
137 self._fileroots = set(self._files)
130
138
131 def __call__(self, fn):
139 def __call__(self, fn):
132 return self.matchfn(fn)
140 return self.matchfn(fn)
133 def __iter__(self):
141 def __iter__(self):
134 for f in self._files:
142 for f in self._files:
135 yield f
143 yield f
136
144
137 # Callbacks related to how the matcher is used by dirstate.walk.
145 # Callbacks related to how the matcher is used by dirstate.walk.
138 # Subscribers to these events must monkeypatch the matcher object.
146 # Subscribers to these events must monkeypatch the matcher object.
139 def bad(self, f, msg):
147 def bad(self, f, msg):
140 '''Callback from dirstate.walk for each explicit file that can't be
148 '''Callback from dirstate.walk for each explicit file that can't be
141 found/accessed, with an error message.'''
149 found/accessed, with an error message.'''
142 pass
150 pass
143
151
144 # If an explicitdir is set, it will be called when an explicitly listed
152 # If an explicitdir is set, it will be called when an explicitly listed
145 # directory is visited.
153 # directory is visited.
146 explicitdir = None
154 explicitdir = None
147
155
148 # If an traversedir is set, it will be called when a directory discovered
156 # If an traversedir is set, it will be called when a directory discovered
149 # by recursive traversal is visited.
157 # by recursive traversal is visited.
150 traversedir = None
158 traversedir = None
151
159
152 def abs(self, f):
160 def abs(self, f):
153 '''Convert a repo path back to path that is relative to the root of the
161 '''Convert a repo path back to path that is relative to the root of the
154 matcher.'''
162 matcher.'''
155 return f
163 return f
156
164
157 def rel(self, f):
165 def rel(self, f):
158 '''Convert repo path back to path that is relative to cwd of matcher.'''
166 '''Convert repo path back to path that is relative to cwd of matcher.'''
159 return util.pathto(self._root, self._cwd, f)
167 return util.pathto(self._root, self._cwd, f)
160
168
161 def uipath(self, f):
169 def uipath(self, f):
162 '''Convert repo path to a display path. If patterns or -I/-X were used
170 '''Convert repo path to a display path. If patterns or -I/-X were used
163 to create this matcher, the display path will be relative to cwd.
171 to create this matcher, the display path will be relative to cwd.
164 Otherwise it is relative to the root of the repo.'''
172 Otherwise it is relative to the root of the repo.'''
165 return (self._pathrestricted and self.rel(f)) or self.abs(f)
173 return (self._pathrestricted and self.rel(f)) or self.abs(f)
166
174
167 def files(self):
175 def files(self):
168 '''Explicitly listed files or patterns or roots:
176 '''Explicitly listed files or patterns or roots:
169 if no patterns or .always(): empty list,
177 if no patterns or .always(): empty list,
170 if exact: list exact files,
178 if exact: list exact files,
171 if not .anypats(): list all files and dirs,
179 if not .anypats(): list all files and dirs,
172 else: optimal roots'''
180 else: optimal roots'''
173 return self._files
181 return self._files
174
182
175 @propertycache
183 @propertycache
176 def _dirs(self):
184 def _dirs(self):
177 return set(util.dirs(self._fileroots)) | set(['.'])
185 return set(util.dirs(self._fileroots)) | set(['.'])
178
186
179 def visitdir(self, dir):
187 def visitdir(self, dir):
188 '''Decides whether a directory should be visited based on whether it
189 has potential matches in it or one of its subdirectories. This is
190 based on the match's primary, included, and excluded patterns.
191
192 This function's behavior is undefined if it has returned False for
193 one of the dir's parent directories.
194 '''
195 if dir in self._excluderoots:
196 return False
197 parentdirs = None
198 if (self._includeroots and dir not in self._includeroots and
199 dir not in self._includedirs):
200 parentdirs = util.finddirs(dir)
201 if not any(parent in self._includeroots for parent in parentdirs):
202 return False
180 return (not self._fileroots or '.' in self._fileroots or
203 return (not self._fileroots or '.' in self._fileroots or
181 dir in self._fileroots or dir in self._dirs or
204 dir in self._fileroots or dir in self._dirs or
182 any(parentdir in self._fileroots
205 any(parentdir in self._fileroots
183 for parentdir in util.finddirs(dir)))
206 for parentdir in parentdirs or util.finddirs(dir)))
184
207
185 def exact(self, f):
208 def exact(self, f):
186 '''Returns True if f is in .files().'''
209 '''Returns True if f is in .files().'''
187 return f in self._fileroots
210 return f in self._fileroots
188
211
189 def anypats(self):
212 def anypats(self):
190 '''Matcher uses patterns or include/exclude.'''
213 '''Matcher uses patterns or include/exclude.'''
191 return self._anypats
214 return self._anypats
192
215
193 def always(self):
216 def always(self):
194 '''Matcher will match everything and .files() will be empty
217 '''Matcher will match everything and .files() will be empty
195 - optimization might be possible and necessary.'''
218 - optimization might be possible and necessary.'''
196 return self._always
219 return self._always
197
220
198 def ispartial(self):
221 def ispartial(self):
199 '''True if the matcher won't always match.
222 '''True if the matcher won't always match.
200
223
201 Although it's just the inverse of _always in this implementation,
224 Although it's just the inverse of _always in this implementation,
202 an extenion such as narrowhg might make it return something
225 an extenion such as narrowhg might make it return something
203 slightly different.'''
226 slightly different.'''
204 return not self._always
227 return not self._always
205
228
206 def isexact(self):
229 def isexact(self):
207 return self.matchfn == self.exact
230 return self.matchfn == self.exact
208
231
209 def _normalize(self, patterns, default, root, cwd, auditor):
232 def _normalize(self, patterns, default, root, cwd, auditor):
210 '''Convert 'kind:pat' from the patterns list to tuples with kind and
233 '''Convert 'kind:pat' from the patterns list to tuples with kind and
211 normalized and rooted patterns and with listfiles expanded.'''
234 normalized and rooted patterns and with listfiles expanded.'''
212 kindpats = []
235 kindpats = []
213 for kind, pat in [_patsplit(p, default) for p in patterns]:
236 for kind, pat in [_patsplit(p, default) for p in patterns]:
214 if kind in ('glob', 'relpath'):
237 if kind in ('glob', 'relpath'):
215 pat = pathutil.canonpath(root, cwd, pat, auditor)
238 pat = pathutil.canonpath(root, cwd, pat, auditor)
216 elif kind in ('relglob', 'path'):
239 elif kind in ('relglob', 'path'):
217 pat = util.normpath(pat)
240 pat = util.normpath(pat)
218 elif kind in ('listfile', 'listfile0'):
241 elif kind in ('listfile', 'listfile0'):
219 try:
242 try:
220 files = util.readfile(pat)
243 files = util.readfile(pat)
221 if kind == 'listfile0':
244 if kind == 'listfile0':
222 files = files.split('\0')
245 files = files.split('\0')
223 else:
246 else:
224 files = files.splitlines()
247 files = files.splitlines()
225 files = [f for f in files if f]
248 files = [f for f in files if f]
226 except EnvironmentError:
249 except EnvironmentError:
227 raise util.Abort(_("unable to read file list (%s)") % pat)
250 raise util.Abort(_("unable to read file list (%s)") % pat)
228 for k, p, source in self._normalize(files, default, root, cwd,
251 for k, p, source in self._normalize(files, default, root, cwd,
229 auditor):
252 auditor):
230 kindpats.append((k, p, pat))
253 kindpats.append((k, p, pat))
231 continue
254 continue
232 elif kind == 'include':
255 elif kind == 'include':
233 try:
256 try:
234 includepats = readpatternfile(pat, self._warn)
257 includepats = readpatternfile(pat, self._warn)
235 for k, p, source in self._normalize(includepats, default,
258 for k, p, source in self._normalize(includepats, default,
236 root, cwd, auditor):
259 root, cwd, auditor):
237 kindpats.append((k, p, source or pat))
260 kindpats.append((k, p, source or pat))
238 except util.Abort, inst:
261 except util.Abort, inst:
239 raise util.Abort('%s: %s' % (pat, inst[0]))
262 raise util.Abort('%s: %s' % (pat, inst[0]))
240 except IOError, inst:
263 except IOError, inst:
241 if self._warn:
264 if self._warn:
242 self._warn(_("skipping unreadable pattern file "
265 self._warn(_("skipping unreadable pattern file "
243 "'%s': %s\n") % (pat, inst.strerror))
266 "'%s': %s\n") % (pat, inst.strerror))
244 continue
267 continue
245 # else: re or relre - which cannot be normalized
268 # else: re or relre - which cannot be normalized
246 kindpats.append((kind, pat, ''))
269 kindpats.append((kind, pat, ''))
247 return kindpats
270 return kindpats
248
271
249 def exact(root, cwd, files):
272 def exact(root, cwd, files):
250 return match(root, cwd, files, exact=True)
273 return match(root, cwd, files, exact=True)
251
274
252 def always(root, cwd):
275 def always(root, cwd):
253 return match(root, cwd, [])
276 return match(root, cwd, [])
254
277
255 class narrowmatcher(match):
278 class narrowmatcher(match):
256 """Adapt a matcher to work on a subdirectory only.
279 """Adapt a matcher to work on a subdirectory only.
257
280
258 The paths are remapped to remove/insert the path as needed:
281 The paths are remapped to remove/insert the path as needed:
259
282
260 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
283 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
261 >>> m2 = narrowmatcher('sub', m1)
284 >>> m2 = narrowmatcher('sub', m1)
262 >>> bool(m2('a.txt'))
285 >>> bool(m2('a.txt'))
263 False
286 False
264 >>> bool(m2('b.txt'))
287 >>> bool(m2('b.txt'))
265 True
288 True
266 >>> bool(m2.matchfn('a.txt'))
289 >>> bool(m2.matchfn('a.txt'))
267 False
290 False
268 >>> bool(m2.matchfn('b.txt'))
291 >>> bool(m2.matchfn('b.txt'))
269 True
292 True
270 >>> m2.files()
293 >>> m2.files()
271 ['b.txt']
294 ['b.txt']
272 >>> m2.exact('b.txt')
295 >>> m2.exact('b.txt')
273 True
296 True
274 >>> util.pconvert(m2.rel('b.txt'))
297 >>> util.pconvert(m2.rel('b.txt'))
275 'sub/b.txt'
298 'sub/b.txt'
276 >>> def bad(f, msg):
299 >>> def bad(f, msg):
277 ... print "%s: %s" % (f, msg)
300 ... print "%s: %s" % (f, msg)
278 >>> m1.bad = bad
301 >>> m1.bad = bad
279 >>> m2.bad('x.txt', 'No such file')
302 >>> m2.bad('x.txt', 'No such file')
280 sub/x.txt: No such file
303 sub/x.txt: No such file
281 >>> m2.abs('c.txt')
304 >>> m2.abs('c.txt')
282 'sub/c.txt'
305 'sub/c.txt'
283 """
306 """
284
307
285 def __init__(self, path, matcher):
308 def __init__(self, path, matcher):
286 self._root = matcher._root
309 self._root = matcher._root
287 self._cwd = matcher._cwd
310 self._cwd = matcher._cwd
288 self._path = path
311 self._path = path
289 self._matcher = matcher
312 self._matcher = matcher
290 self._always = matcher._always
313 self._always = matcher._always
291 self._pathrestricted = matcher._pathrestricted
314 self._pathrestricted = matcher._pathrestricted
292
315
293 self._files = [f[len(path) + 1:] for f in matcher._files
316 self._files = [f[len(path) + 1:] for f in matcher._files
294 if f.startswith(path + "/")]
317 if f.startswith(path + "/")]
295
318
296 # If the parent repo had a path to this subrepo and no patterns are
319 # If the parent repo had a path to this subrepo and no patterns are
297 # specified, this submatcher always matches.
320 # specified, this submatcher always matches.
298 if not self._always and not matcher._anypats:
321 if not self._always and not matcher._anypats:
299 self._always = any(f == path for f in matcher._files)
322 self._always = any(f == path for f in matcher._files)
300
323
301 self._anypats = matcher._anypats
324 self._anypats = matcher._anypats
302 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
325 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
303 self._fileroots = set(self._files)
326 self._fileroots = set(self._files)
304
327
305 def abs(self, f):
328 def abs(self, f):
306 return self._matcher.abs(self._path + "/" + f)
329 return self._matcher.abs(self._path + "/" + f)
307
330
308 def bad(self, f, msg):
331 def bad(self, f, msg):
309 self._matcher.bad(self._path + "/" + f, msg)
332 self._matcher.bad(self._path + "/" + f, msg)
310
333
311 def rel(self, f):
334 def rel(self, f):
312 return self._matcher.rel(self._path + "/" + f)
335 return self._matcher.rel(self._path + "/" + f)
313
336
314 class icasefsmatcher(match):
337 class icasefsmatcher(match):
315 """A matcher for wdir on case insensitive filesystems, which normalizes the
338 """A matcher for wdir on case insensitive filesystems, which normalizes the
316 given patterns to the case in the filesystem.
339 given patterns to the case in the filesystem.
317 """
340 """
318
341
319 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
342 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
320 ctx, listsubrepos=False):
343 ctx, listsubrepos=False):
321 init = super(icasefsmatcher, self).__init__
344 init = super(icasefsmatcher, self).__init__
322 self._dsnormalize = ctx.repo().dirstate.normalize
345 self._dsnormalize = ctx.repo().dirstate.normalize
323
346
324 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
347 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
325 ctx=ctx, listsubrepos=listsubrepos)
348 ctx=ctx, listsubrepos=listsubrepos)
326
349
327 # m.exact(file) must be based off of the actual user input, otherwise
350 # m.exact(file) must be based off of the actual user input, otherwise
328 # inexact case matches are treated as exact, and not noted without -v.
351 # inexact case matches are treated as exact, and not noted without -v.
329 if self._files:
352 if self._files:
330 self._fileroots = set(_roots(self._kp))
353 self._fileroots = set(_roots(self._kp))
331
354
332 def _normalize(self, patterns, default, root, cwd, auditor):
355 def _normalize(self, patterns, default, root, cwd, auditor):
333 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
356 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
334 root, cwd, auditor)
357 root, cwd, auditor)
335 kindpats = []
358 kindpats = []
336 for kind, pats, source in self._kp:
359 for kind, pats, source in self._kp:
337 if kind not in ('re', 'relre'): # regex can't be normalized
360 if kind not in ('re', 'relre'): # regex can't be normalized
338 pats = self._dsnormalize(pats)
361 pats = self._dsnormalize(pats)
339 kindpats.append((kind, pats, source))
362 kindpats.append((kind, pats, source))
340 return kindpats
363 return kindpats
341
364
342 def patkind(pattern, default=None):
365 def patkind(pattern, default=None):
343 '''If pattern is 'kind:pat' with a known kind, return kind.'''
366 '''If pattern is 'kind:pat' with a known kind, return kind.'''
344 return _patsplit(pattern, default)[0]
367 return _patsplit(pattern, default)[0]
345
368
346 def _patsplit(pattern, default):
369 def _patsplit(pattern, default):
347 """Split a string into the optional pattern kind prefix and the actual
370 """Split a string into the optional pattern kind prefix and the actual
348 pattern."""
371 pattern."""
349 if ':' in pattern:
372 if ':' in pattern:
350 kind, pat = pattern.split(':', 1)
373 kind, pat = pattern.split(':', 1)
351 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
374 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
352 'listfile', 'listfile0', 'set', 'include'):
375 'listfile', 'listfile0', 'set', 'include'):
353 return kind, pat
376 return kind, pat
354 return default, pattern
377 return default, pattern
355
378
356 def _globre(pat):
379 def _globre(pat):
357 r'''Convert an extended glob string to a regexp string.
380 r'''Convert an extended glob string to a regexp string.
358
381
359 >>> print _globre(r'?')
382 >>> print _globre(r'?')
360 .
383 .
361 >>> print _globre(r'*')
384 >>> print _globre(r'*')
362 [^/]*
385 [^/]*
363 >>> print _globre(r'**')
386 >>> print _globre(r'**')
364 .*
387 .*
365 >>> print _globre(r'**/a')
388 >>> print _globre(r'**/a')
366 (?:.*/)?a
389 (?:.*/)?a
367 >>> print _globre(r'a/**/b')
390 >>> print _globre(r'a/**/b')
368 a\/(?:.*/)?b
391 a\/(?:.*/)?b
369 >>> print _globre(r'[a*?!^][^b][!c]')
392 >>> print _globre(r'[a*?!^][^b][!c]')
370 [a*?!^][\^b][^c]
393 [a*?!^][\^b][^c]
371 >>> print _globre(r'{a,b}')
394 >>> print _globre(r'{a,b}')
372 (?:a|b)
395 (?:a|b)
373 >>> print _globre(r'.\*\?')
396 >>> print _globre(r'.\*\?')
374 \.\*\?
397 \.\*\?
375 '''
398 '''
376 i, n = 0, len(pat)
399 i, n = 0, len(pat)
377 res = ''
400 res = ''
378 group = 0
401 group = 0
379 escape = util.re.escape
402 escape = util.re.escape
380 def peek():
403 def peek():
381 return i < n and pat[i]
404 return i < n and pat[i]
382 while i < n:
405 while i < n:
383 c = pat[i]
406 c = pat[i]
384 i += 1
407 i += 1
385 if c not in '*?[{},\\':
408 if c not in '*?[{},\\':
386 res += escape(c)
409 res += escape(c)
387 elif c == '*':
410 elif c == '*':
388 if peek() == '*':
411 if peek() == '*':
389 i += 1
412 i += 1
390 if peek() == '/':
413 if peek() == '/':
391 i += 1
414 i += 1
392 res += '(?:.*/)?'
415 res += '(?:.*/)?'
393 else:
416 else:
394 res += '.*'
417 res += '.*'
395 else:
418 else:
396 res += '[^/]*'
419 res += '[^/]*'
397 elif c == '?':
420 elif c == '?':
398 res += '.'
421 res += '.'
399 elif c == '[':
422 elif c == '[':
400 j = i
423 j = i
401 if j < n and pat[j] in '!]':
424 if j < n and pat[j] in '!]':
402 j += 1
425 j += 1
403 while j < n and pat[j] != ']':
426 while j < n and pat[j] != ']':
404 j += 1
427 j += 1
405 if j >= n:
428 if j >= n:
406 res += '\\['
429 res += '\\['
407 else:
430 else:
408 stuff = pat[i:j].replace('\\','\\\\')
431 stuff = pat[i:j].replace('\\','\\\\')
409 i = j + 1
432 i = j + 1
410 if stuff[0] == '!':
433 if stuff[0] == '!':
411 stuff = '^' + stuff[1:]
434 stuff = '^' + stuff[1:]
412 elif stuff[0] == '^':
435 elif stuff[0] == '^':
413 stuff = '\\' + stuff
436 stuff = '\\' + stuff
414 res = '%s[%s]' % (res, stuff)
437 res = '%s[%s]' % (res, stuff)
415 elif c == '{':
438 elif c == '{':
416 group += 1
439 group += 1
417 res += '(?:'
440 res += '(?:'
418 elif c == '}' and group:
441 elif c == '}' and group:
419 res += ')'
442 res += ')'
420 group -= 1
443 group -= 1
421 elif c == ',' and group:
444 elif c == ',' and group:
422 res += '|'
445 res += '|'
423 elif c == '\\':
446 elif c == '\\':
424 p = peek()
447 p = peek()
425 if p:
448 if p:
426 i += 1
449 i += 1
427 res += escape(p)
450 res += escape(p)
428 else:
451 else:
429 res += escape(c)
452 res += escape(c)
430 else:
453 else:
431 res += escape(c)
454 res += escape(c)
432 return res
455 return res
433
456
434 def _regex(kind, pat, globsuffix):
457 def _regex(kind, pat, globsuffix):
435 '''Convert a (normalized) pattern of any kind into a regular expression.
458 '''Convert a (normalized) pattern of any kind into a regular expression.
436 globsuffix is appended to the regexp of globs.'''
459 globsuffix is appended to the regexp of globs.'''
437 if not pat:
460 if not pat:
438 return ''
461 return ''
439 if kind == 're':
462 if kind == 're':
440 return pat
463 return pat
441 if kind == 'path':
464 if kind == 'path':
442 return '^' + util.re.escape(pat) + '(?:/|$)'
465 return '^' + util.re.escape(pat) + '(?:/|$)'
443 if kind == 'relglob':
466 if kind == 'relglob':
444 return '(?:|.*/)' + _globre(pat) + globsuffix
467 return '(?:|.*/)' + _globre(pat) + globsuffix
445 if kind == 'relpath':
468 if kind == 'relpath':
446 return util.re.escape(pat) + '(?:/|$)'
469 return util.re.escape(pat) + '(?:/|$)'
447 if kind == 'relre':
470 if kind == 'relre':
448 if pat.startswith('^'):
471 if pat.startswith('^'):
449 return pat
472 return pat
450 return '.*' + pat
473 return '.*' + pat
451 return _globre(pat) + globsuffix
474 return _globre(pat) + globsuffix
452
475
453 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos):
476 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos):
454 '''Return regexp string and a matcher function for kindpats.
477 '''Return regexp string and a matcher function for kindpats.
455 globsuffix is appended to the regexp of globs.'''
478 globsuffix is appended to the regexp of globs.'''
456 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
479 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
457 if not kindpats:
480 if not kindpats:
458 return "", fset.__contains__
481 return "", fset.__contains__
459
482
460 regex, mf = _buildregexmatch(kindpats, globsuffix)
483 regex, mf = _buildregexmatch(kindpats, globsuffix)
461 if fset:
484 if fset:
462 return regex, lambda f: f in fset or mf(f)
485 return regex, lambda f: f in fset or mf(f)
463 return regex, mf
486 return regex, mf
464
487
465 def _buildregexmatch(kindpats, globsuffix):
488 def _buildregexmatch(kindpats, globsuffix):
466 """Build a match function from a list of kinds and kindpats,
489 """Build a match function from a list of kinds and kindpats,
467 return regexp string and a matcher function."""
490 return regexp string and a matcher function."""
468 try:
491 try:
469 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
492 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
470 for (k, p, s) in kindpats])
493 for (k, p, s) in kindpats])
471 if len(regex) > 20000:
494 if len(regex) > 20000:
472 raise OverflowError
495 raise OverflowError
473 return regex, _rematcher(regex)
496 return regex, _rematcher(regex)
474 except OverflowError:
497 except OverflowError:
475 # We're using a Python with a tiny regex engine and we
498 # We're using a Python with a tiny regex engine and we
476 # made it explode, so we'll divide the pattern list in two
499 # made it explode, so we'll divide the pattern list in two
477 # until it works
500 # until it works
478 l = len(kindpats)
501 l = len(kindpats)
479 if l < 2:
502 if l < 2:
480 raise
503 raise
481 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
504 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
482 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
505 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
483 return regex, lambda s: a(s) or b(s)
506 return regex, lambda s: a(s) or b(s)
484 except re.error:
507 except re.error:
485 for k, p, s in kindpats:
508 for k, p, s in kindpats:
486 try:
509 try:
487 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
510 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
488 except re.error:
511 except re.error:
489 if s:
512 if s:
490 raise util.Abort(_("%s: invalid pattern (%s): %s") %
513 raise util.Abort(_("%s: invalid pattern (%s): %s") %
491 (s, k, p))
514 (s, k, p))
492 else:
515 else:
493 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
516 raise util.Abort(_("invalid pattern (%s): %s") % (k, p))
494 raise util.Abort(_("invalid pattern"))
517 raise util.Abort(_("invalid pattern"))
495
518
496 def _roots(kindpats):
519 def _roots(kindpats):
497 '''return roots and exact explicitly listed files from patterns
520 '''return roots and exact explicitly listed files from patterns
498
521
499 >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')])
522 >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')])
500 ['g', 'g', '.']
523 ['g', 'g', '.']
501 >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
524 >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
502 ['r', 'p/p', '.']
525 ['r', 'p/p', '.']
503 >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
526 >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
504 ['.', '.', '.']
527 ['.', '.', '.']
505 '''
528 '''
506 r = []
529 r = []
507 for kind, pat, source in kindpats:
530 for kind, pat, source in kindpats:
508 if kind == 'glob': # find the non-glob prefix
531 if kind == 'glob': # find the non-glob prefix
509 root = []
532 root = []
510 for p in pat.split('/'):
533 for p in pat.split('/'):
511 if '[' in p or '{' in p or '*' in p or '?' in p:
534 if '[' in p or '{' in p or '*' in p or '?' in p:
512 break
535 break
513 root.append(p)
536 root.append(p)
514 r.append('/'.join(root) or '.')
537 r.append('/'.join(root) or '.')
515 elif kind in ('relpath', 'path'):
538 elif kind in ('relpath', 'path'):
516 r.append(pat or '.')
539 r.append(pat or '.')
517 else: # relglob, re, relre
540 else: # relglob, re, relre
518 r.append('.')
541 r.append('.')
519 return r
542 return r
520
543
521 def _anypats(kindpats):
544 def _anypats(kindpats):
522 for kind, pat, source in kindpats:
545 for kind, pat, source in kindpats:
523 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
546 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
524 return True
547 return True
525
548
526 _commentre = None
549 _commentre = None
527
550
528 def readpatternfile(filepath, warn):
551 def readpatternfile(filepath, warn):
529 '''parse a pattern file, returning a list of
552 '''parse a pattern file, returning a list of
530 patterns. These patterns should be given to compile()
553 patterns. These patterns should be given to compile()
531 to be validated and converted into a match function.
554 to be validated and converted into a match function.
532
555
533 trailing white space is dropped.
556 trailing white space is dropped.
534 the escape character is backslash.
557 the escape character is backslash.
535 comments start with #.
558 comments start with #.
536 empty lines are skipped.
559 empty lines are skipped.
537
560
538 lines can be of the following formats:
561 lines can be of the following formats:
539
562
540 syntax: regexp # defaults following lines to non-rooted regexps
563 syntax: regexp # defaults following lines to non-rooted regexps
541 syntax: glob # defaults following lines to non-rooted globs
564 syntax: glob # defaults following lines to non-rooted globs
542 re:pattern # non-rooted regular expression
565 re:pattern # non-rooted regular expression
543 glob:pattern # non-rooted glob
566 glob:pattern # non-rooted glob
544 pattern # pattern of the current default type'''
567 pattern # pattern of the current default type'''
545
568
546 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
569 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
547 'include': 'include'}
570 'include': 'include'}
548 syntax = 'relre:'
571 syntax = 'relre:'
549 patterns = []
572 patterns = []
550
573
551 fp = open(filepath)
574 fp = open(filepath)
552 for line in fp:
575 for line in fp:
553 if "#" in line:
576 if "#" in line:
554 global _commentre
577 global _commentre
555 if not _commentre:
578 if not _commentre:
556 _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
579 _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*')
557 # remove comments prefixed by an even number of escapes
580 # remove comments prefixed by an even number of escapes
558 line = _commentre.sub(r'\1', line)
581 line = _commentre.sub(r'\1', line)
559 # fixup properly escaped comments that survived the above
582 # fixup properly escaped comments that survived the above
560 line = line.replace("\\#", "#")
583 line = line.replace("\\#", "#")
561 line = line.rstrip()
584 line = line.rstrip()
562 if not line:
585 if not line:
563 continue
586 continue
564
587
565 if line.startswith('syntax:'):
588 if line.startswith('syntax:'):
566 s = line[7:].strip()
589 s = line[7:].strip()
567 try:
590 try:
568 syntax = syntaxes[s]
591 syntax = syntaxes[s]
569 except KeyError:
592 except KeyError:
570 if warn:
593 if warn:
571 warn(_("%s: ignoring invalid syntax '%s'\n") %
594 warn(_("%s: ignoring invalid syntax '%s'\n") %
572 (filepath, s))
595 (filepath, s))
573 continue
596 continue
574
597
575 linesyntax = syntax
598 linesyntax = syntax
576 for s, rels in syntaxes.iteritems():
599 for s, rels in syntaxes.iteritems():
577 if line.startswith(rels):
600 if line.startswith(rels):
578 linesyntax = rels
601 linesyntax = rels
579 line = line[len(rels):]
602 line = line[len(rels):]
580 break
603 break
581 elif line.startswith(s+':'):
604 elif line.startswith(s+':'):
582 linesyntax = rels
605 linesyntax = rels
583 line = line[len(s) + 1:]
606 line = line[len(s) + 1:]
584 break
607 break
585 patterns.append(linesyntax + line)
608 patterns.append(linesyntax + line)
586 fp.close()
609 fp.close()
587 return patterns
610 return patterns
@@ -1,282 +1,379
1
1
2 Set up repo
2 Set up repo
3
3
4 $ hg --config experimental.treemanifest=True init repo
4 $ hg --config experimental.treemanifest=True init repo
5 $ cd repo
5 $ cd repo
6
6
7 Requirements get set on init
7 Requirements get set on init
8
8
9 $ grep treemanifest .hg/requires
9 $ grep treemanifest .hg/requires
10 treemanifest
10 treemanifest
11
11
12 Without directories, looks like any other repo
12 Without directories, looks like any other repo
13
13
14 $ echo 0 > a
14 $ echo 0 > a
15 $ echo 0 > b
15 $ echo 0 > b
16 $ hg ci -Aqm initial
16 $ hg ci -Aqm initial
17 $ hg debugdata -m 0
17 $ hg debugdata -m 0
18 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
18 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
19 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
19 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
20
20
21 Submanifest is stored in separate revlog
21 Submanifest is stored in separate revlog
22
22
23 $ mkdir dir1
23 $ mkdir dir1
24 $ echo 1 > dir1/a
24 $ echo 1 > dir1/a
25 $ echo 1 > dir1/b
25 $ echo 1 > dir1/b
26 $ echo 1 > e
26 $ echo 1 > e
27 $ hg ci -Aqm 'add dir1'
27 $ hg ci -Aqm 'add dir1'
28 $ hg debugdata -m 1
28 $ hg debugdata -m 1
29 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
29 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
30 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
30 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
31 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc)
31 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44ed (esc)
32 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
32 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
33 $ hg debugdata --dir dir1 0
33 $ hg debugdata --dir dir1 0
34 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
34 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
35 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
35 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
36
36
37 Can add nested directories
37 Can add nested directories
38
38
39 $ mkdir dir1/dir1
39 $ mkdir dir1/dir1
40 $ echo 2 > dir1/dir1/a
40 $ echo 2 > dir1/dir1/a
41 $ echo 2 > dir1/dir1/b
41 $ echo 2 > dir1/dir1/b
42 $ mkdir dir1/dir2
42 $ mkdir dir1/dir2
43 $ echo 2 > dir1/dir2/a
43 $ echo 2 > dir1/dir2/a
44 $ echo 2 > dir1/dir2/b
44 $ echo 2 > dir1/dir2/b
45 $ hg ci -Aqm 'add dir1/dir1'
45 $ hg ci -Aqm 'add dir1/dir1'
46 $ hg files -r .
46 $ hg files -r .
47 a
47 a
48 b
48 b
49 dir1/a (glob)
49 dir1/a (glob)
50 dir1/b (glob)
50 dir1/b (glob)
51 dir1/dir1/a (glob)
51 dir1/dir1/a (glob)
52 dir1/dir1/b (glob)
52 dir1/dir1/b (glob)
53 dir1/dir2/a (glob)
53 dir1/dir2/a (glob)
54 dir1/dir2/b (glob)
54 dir1/dir2/b (glob)
55 e
55 e
56
56
57 Revision is not created for unchanged directory
57 Revision is not created for unchanged directory
58
58
59 $ mkdir dir2
59 $ mkdir dir2
60 $ echo 3 > dir2/a
60 $ echo 3 > dir2/a
61 $ hg add dir2
61 $ hg add dir2
62 adding dir2/a (glob)
62 adding dir2/a (glob)
63 $ hg debugindex --dir dir1 > before
63 $ hg debugindex --dir dir1 > before
64 $ hg ci -qm 'add dir2'
64 $ hg ci -qm 'add dir2'
65 $ hg debugindex --dir dir1 > after
65 $ hg debugindex --dir dir1 > after
66 $ diff before after
66 $ diff before after
67 $ rm before after
67 $ rm before after
68
68
69 Removing directory does not create an revlog entry
69 Removing directory does not create an revlog entry
70
70
71 $ hg rm dir1/dir1
71 $ hg rm dir1/dir1
72 removing dir1/dir1/a (glob)
72 removing dir1/dir1/a (glob)
73 removing dir1/dir1/b (glob)
73 removing dir1/dir1/b (glob)
74 $ hg debugindex --dir dir1/dir1 > before
74 $ hg debugindex --dir dir1/dir1 > before
75 $ hg ci -qm 'remove dir1/dir1'
75 $ hg ci -qm 'remove dir1/dir1'
76 $ hg debugindex --dir dir1/dir1 > after
76 $ hg debugindex --dir dir1/dir1 > after
77 $ diff before after
77 $ diff before after
78 $ rm before after
78 $ rm before after
79
79
80 Check that hg files (calls treemanifest.walk()) works
80 Check that hg files (calls treemanifest.walk()) works
81 without loading all directory revlogs
81 without loading all directory revlogs
82
82
83 $ hg co 'desc("add dir2")'
83 $ hg co 'desc("add dir2")'
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
85 $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
86 $ hg files -r . dir1
86 $ hg files -r . dir1
87 dir1/a (glob)
87 dir1/a (glob)
88 dir1/b (glob)
88 dir1/b (glob)
89 dir1/dir1/a (glob)
89 dir1/dir1/a (glob)
90 dir1/dir1/b (glob)
90 dir1/dir1/b (glob)
91 dir1/dir2/a (glob)
91 dir1/dir2/a (glob)
92 dir1/dir2/b (glob)
92 dir1/dir2/b (glob)
93
93
94 Check that status between revisions works (calls treemanifest.matches())
94 Check that status between revisions works (calls treemanifest.matches())
95 without loading all directory revlogs
95 without loading all directory revlogs
96
96
97 $ hg status --rev 'desc("add dir1")' --rev . dir1
97 $ hg status --rev 'desc("add dir1")' --rev . dir1
98 A dir1/dir1/a
98 A dir1/dir1/a
99 A dir1/dir1/b
99 A dir1/dir1/b
100 A dir1/dir2/a
100 A dir1/dir2/a
101 A dir1/dir2/b
101 A dir1/dir2/b
102 $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
102 $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
103
103
104 Merge creates 2-parent revision of directory revlog
104 Merge creates 2-parent revision of directory revlog
105
105
106 $ echo 5 > dir1/a
106 $ echo 5 > dir1/a
107 $ hg ci -Aqm 'modify dir1/a'
107 $ hg ci -Aqm 'modify dir1/a'
108 $ hg co '.^'
108 $ hg co '.^'
109 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
110 $ echo 6 > dir1/b
110 $ echo 6 > dir1/b
111 $ hg ci -Aqm 'modify dir1/b'
111 $ hg ci -Aqm 'modify dir1/b'
112 $ hg merge 'desc("modify dir1/a")'
112 $ hg merge 'desc("modify dir1/a")'
113 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
113 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
114 (branch merge, don't forget to commit)
114 (branch merge, don't forget to commit)
115 $ hg ci -m 'conflict-free merge involving dir1/'
115 $ hg ci -m 'conflict-free merge involving dir1/'
116 $ cat dir1/a
116 $ cat dir1/a
117 5
117 5
118 $ cat dir1/b
118 $ cat dir1/b
119 6
119 6
120 $ hg debugindex --dir dir1
120 $ hg debugindex --dir dir1
121 rev offset length base linkrev nodeid p1 p2
121 rev offset length base linkrev nodeid p1 p2
122 0 0 54 0 1 8b3ffd73f901 000000000000 000000000000
122 0 0 54 0 1 8b3ffd73f901 000000000000 000000000000
123 1 54 68 0 2 b66d046c644f 8b3ffd73f901 000000000000
123 1 54 68 0 2 b66d046c644f 8b3ffd73f901 000000000000
124 2 122 12 0 4 b87265673c8a b66d046c644f 000000000000
124 2 122 12 0 4 b87265673c8a b66d046c644f 000000000000
125 3 134 95 0 5 aa5d3adcec72 b66d046c644f 000000000000
125 3 134 95 0 5 aa5d3adcec72 b66d046c644f 000000000000
126 4 229 81 0 6 e29b066b91ad b66d046c644f 000000000000
126 4 229 81 0 6 e29b066b91ad b66d046c644f 000000000000
127 5 310 107 5 7 a120ce2b83f5 e29b066b91ad aa5d3adcec72
127 5 310 107 5 7 a120ce2b83f5 e29b066b91ad aa5d3adcec72
128
128
129 Merge keeping directory from parent 1 does not create revlog entry. (Note that
129 Merge keeping directory from parent 1 does not create revlog entry. (Note that
130 dir1's manifest does change, but only because dir1/a's filelog changes.)
130 dir1's manifest does change, but only because dir1/a's filelog changes.)
131
131
132 $ hg co 'desc("add dir2")'
132 $ hg co 'desc("add dir2")'
133 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
134 $ echo 8 > dir2/a
134 $ echo 8 > dir2/a
135 $ hg ci -m 'modify dir2/a'
135 $ hg ci -m 'modify dir2/a'
136 created new head
136 created new head
137
137
138 $ hg debugindex --dir dir2 > before
138 $ hg debugindex --dir dir2 > before
139 $ hg merge 'desc("modify dir1/a")'
139 $ hg merge 'desc("modify dir1/a")'
140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 (branch merge, don't forget to commit)
141 (branch merge, don't forget to commit)
142 $ hg revert -r 'desc("modify dir2/a")' .
142 $ hg revert -r 'desc("modify dir2/a")' .
143 reverting dir1/a (glob)
143 reverting dir1/a (glob)
144 $ hg ci -m 'merge, keeping parent 1'
144 $ hg ci -m 'merge, keeping parent 1'
145 $ hg debugindex --dir dir2 > after
145 $ hg debugindex --dir dir2 > after
146 $ diff before after
146 $ diff before after
147 $ rm before after
147 $ rm before after
148
148
149 Merge keeping directory from parent 2 does not create revlog entry. (Note that
149 Merge keeping directory from parent 2 does not create revlog entry. (Note that
150 dir2's manifest does change, but only because dir2/a's filelog changes.)
150 dir2's manifest does change, but only because dir2/a's filelog changes.)
151
151
152 $ hg co 'desc("modify dir2/a")'
152 $ hg co 'desc("modify dir2/a")'
153 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
153 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 $ hg debugindex --dir dir1 > before
154 $ hg debugindex --dir dir1 > before
155 $ hg merge 'desc("modify dir1/a")'
155 $ hg merge 'desc("modify dir1/a")'
156 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
156 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
157 (branch merge, don't forget to commit)
157 (branch merge, don't forget to commit)
158 $ hg revert -r 'desc("modify dir1/a")' .
158 $ hg revert -r 'desc("modify dir1/a")' .
159 reverting dir2/a (glob)
159 reverting dir2/a (glob)
160 $ hg ci -m 'merge, keeping parent 2'
160 $ hg ci -m 'merge, keeping parent 2'
161 created new head
161 created new head
162 $ hg debugindex --dir dir1 > after
162 $ hg debugindex --dir dir1 > after
163 $ diff before after
163 $ diff before after
164 $ rm before after
164 $ rm before after
165
165
166 Create flat source repo for tests with mixed flat/tree manifests
166 Create flat source repo for tests with mixed flat/tree manifests
167
167
168 $ cd ..
168 $ cd ..
169 $ hg init repo-flat
169 $ hg init repo-flat
170 $ cd repo-flat
170 $ cd repo-flat
171
171
172 Create a few commits with flat manifest
172 Create a few commits with flat manifest
173
173
174 $ echo 0 > a
174 $ echo 0 > a
175 $ echo 0 > b
175 $ echo 0 > b
176 $ echo 0 > e
176 $ echo 0 > e
177 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
177 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
178 > do
178 > do
179 > mkdir $d
179 > mkdir $d
180 > echo 0 > $d/a
180 > echo 0 > $d/a
181 > echo 0 > $d/b
181 > echo 0 > $d/b
182 > done
182 > done
183 $ hg ci -Aqm initial
183 $ hg ci -Aqm initial
184
184
185 $ echo 1 > a
185 $ echo 1 > a
186 $ echo 1 > dir1/a
186 $ echo 1 > dir1/a
187 $ echo 1 > dir1/dir1/a
187 $ echo 1 > dir1/dir1/a
188 $ hg ci -Aqm 'modify on branch 1'
188 $ hg ci -Aqm 'modify on branch 1'
189
189
190 $ hg co 0
190 $ hg co 0
191 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
191 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
192 $ echo 2 > b
192 $ echo 2 > b
193 $ echo 2 > dir1/b
193 $ echo 2 > dir1/b
194 $ echo 2 > dir1/dir1/b
194 $ echo 2 > dir1/dir1/b
195 $ hg ci -Aqm 'modify on branch 2'
195 $ hg ci -Aqm 'modify on branch 2'
196
196
197 $ hg merge 1
197 $ hg merge 1
198 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
198 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
199 (branch merge, don't forget to commit)
199 (branch merge, don't forget to commit)
200 $ hg ci -m 'merge of flat manifests to new flat manifest'
200 $ hg ci -m 'merge of flat manifests to new flat manifest'
201
201
202 Create clone with tree manifests enabled
202 Create clone with tree manifests enabled
203
203
204 $ cd ..
204 $ cd ..
205 $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed
205 $ hg clone --pull --config experimental.treemanifest=1 repo-flat repo-mixed
206 requesting all changes
206 requesting all changes
207 adding changesets
207 adding changesets
208 adding manifests
208 adding manifests
209 adding file changes
209 adding file changes
210 added 4 changesets with 17 changes to 11 files
210 added 4 changesets with 17 changes to 11 files
211 updating to branch default
211 updating to branch default
212 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
212 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
213 $ cd repo-mixed
213 $ cd repo-mixed
214 $ test -f .hg/store/meta
214 $ test -f .hg/store/meta
215 [1]
215 [1]
216 $ grep treemanifest .hg/requires
216 $ grep treemanifest .hg/requires
217 treemanifest
217 treemanifest
218
218
219 Commit should store revlog per directory
219 Commit should store revlog per directory
220
220
221 $ hg co 1
221 $ hg co 1
222 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 $ echo 3 > a
223 $ echo 3 > a
224 $ echo 3 > dir1/a
224 $ echo 3 > dir1/a
225 $ echo 3 > dir1/dir1/a
225 $ echo 3 > dir1/dir1/a
226 $ hg ci -m 'first tree'
226 $ hg ci -m 'first tree'
227 created new head
227 created new head
228 $ find .hg/store/meta | sort
228 $ find .hg/store/meta | sort
229 .hg/store/meta
229 .hg/store/meta
230 .hg/store/meta/dir1
230 .hg/store/meta/dir1
231 .hg/store/meta/dir1/00manifest.i
231 .hg/store/meta/dir1/00manifest.i
232 .hg/store/meta/dir1/dir1
232 .hg/store/meta/dir1/dir1
233 .hg/store/meta/dir1/dir1/00manifest.i
233 .hg/store/meta/dir1/dir1/00manifest.i
234 .hg/store/meta/dir1/dir2
234 .hg/store/meta/dir1/dir2
235 .hg/store/meta/dir1/dir2/00manifest.i
235 .hg/store/meta/dir1/dir2/00manifest.i
236 .hg/store/meta/dir2
236 .hg/store/meta/dir2
237 .hg/store/meta/dir2/00manifest.i
237 .hg/store/meta/dir2/00manifest.i
238
238
239 Merge of two trees
239 Merge of two trees
240
240
241 $ hg co 2
241 $ hg co 2
242 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
242 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
243 $ hg merge 1
243 $ hg merge 1
244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 (branch merge, don't forget to commit)
245 (branch merge, don't forget to commit)
246 $ hg ci -m 'merge of flat manifests to new tree manifest'
246 $ hg ci -m 'merge of flat manifests to new tree manifest'
247 created new head
247 created new head
248 $ hg diff -r 3
248 $ hg diff -r 3
249
249
250 Parent of tree root manifest should be flat manifest, and two for merge
250 Parent of tree root manifest should be flat manifest, and two for merge
251
251
252 $ hg debugindex -m
252 $ hg debugindex -m
253 rev offset length base linkrev nodeid p1 p2
253 rev offset length base linkrev nodeid p1 p2
254 0 0 80 0 0 40536115ed9e 000000000000 000000000000
254 0 0 80 0 0 40536115ed9e 000000000000 000000000000
255 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
255 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
256 2 163 103 0 2 5d9b9da231a2 40536115ed9e 000000000000
256 2 163 103 0 2 5d9b9da231a2 40536115ed9e 000000000000
257 3 266 83 0 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
257 3 266 83 0 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
258 4 349 132 4 4 c05a51345f86 f3376063c255 000000000000
258 4 349 132 4 4 c05a51345f86 f3376063c255 000000000000
259 5 481 110 4 5 82594b1f557d 5d9b9da231a2 f3376063c255
259 5 481 110 4 5 82594b1f557d 5d9b9da231a2 f3376063c255
260
260
261
261
262 Status across flat/tree boundary should work
262 Status across flat/tree boundary should work
263
263
264 $ hg status --rev '.^' --rev .
264 $ hg status --rev '.^' --rev .
265 M a
265 M a
266 M dir1/a
266 M dir1/a
267 M dir1/dir1/a
267 M dir1/dir1/a
268
268
269
269
270 Turning off treemanifest config has no effect
270 Turning off treemanifest config has no effect
271
271
272 $ hg debugindex .hg/store/meta/dir1/00manifest.i
272 $ hg debugindex .hg/store/meta/dir1/00manifest.i
273 rev offset length base linkrev nodeid p1 p2
273 rev offset length base linkrev nodeid p1 p2
274 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
274 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
275 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
275 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
276 $ echo 2 > dir1/a
276 $ echo 2 > dir1/a
277 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
277 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
278 $ hg debugindex .hg/store/meta/dir1/00manifest.i
278 $ hg debugindex .hg/store/meta/dir1/00manifest.i
279 rev offset length base linkrev nodeid p1 p2
279 rev offset length base linkrev nodeid p1 p2
280 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
280 0 0 125 0 4 63c9c0557d24 000000000000 000000000000
281 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
281 1 125 109 0 5 23d12a1f6e0e 000000000000 000000000000
282 2 234 55 0 6 3cb2d87b4250 23d12a1f6e0e 000000000000
282 2 234 55 0 6 3cb2d87b4250 23d12a1f6e0e 000000000000
283
284 Create deeper repo with tree manifests.
285
286 $ cd ..
287 $ hg --config experimental.treemanifest=True init deeprepo
288 $ cd deeprepo
289
290 $ mkdir a
291 $ mkdir b
292 $ mkdir b/bar
293 $ mkdir b/bar/orange
294 $ mkdir b/bar/orange/fly
295 $ mkdir b/foo
296 $ mkdir b/foo/apple
297 $ mkdir b/foo/apple/bees
298
299 $ touch a/one.txt
300 $ touch a/two.txt
301 $ touch b/bar/fruits.txt
302 $ touch b/bar/orange/fly/gnat.py
303 $ touch b/bar/orange/fly/housefly.txt
304 $ touch b/foo/apple/bees/flower.py
305 $ touch c.txt
306 $ touch d.py
307
308 $ hg ci -Aqm 'initial'
309
310 We'll see that visitdir works by removing some treemanifest revlogs and running
311 the files command with various parameters.
312
313 Test files from the root.
314
315 $ hg files -r .
316 a/one.txt
317 a/two.txt
318 b/bar/fruits.txt
319 b/bar/orange/fly/gnat.py
320 b/bar/orange/fly/housefly.txt
321 b/foo/apple/bees/flower.py
322 c.txt
323 d.py
324
325 Test files for a subdirectory.
326
327 $ mv .hg/store/meta/a oldmf
328 $ hg files -r . b
329 b/bar/fruits.txt
330 b/bar/orange/fly/gnat.py
331 b/bar/orange/fly/housefly.txt
332 b/foo/apple/bees/flower.py
333 $ mv oldmf .hg/store/meta/a
334
335 Test files with just includes and excludes.
336
337 $ mv .hg/store/meta/a oldmf
338 $ mv .hg/store/meta/b/bar/orange/fly oldmf2
339 $ mv .hg/store/meta/b/foo/apple/bees oldmf3
340 $ hg files -r . -I b/bar -X b/bar/orange/fly -I b/foo -X b/foo/apple/bees
341 b/bar/fruits.txt
342 $ mv oldmf .hg/store/meta/a
343 $ mv oldmf2 .hg/store/meta/b/bar/orange/fly
344 $ mv oldmf3 .hg/store/meta/b/foo/apple/bees
345
346 Test files for a subdirectory, excluding a directory within it.
347
348 $ mv .hg/store/meta/a oldmf
349 $ mv .hg/store/meta/b/foo oldmf2
350 $ hg files -r . -X b/foo b
351 b/bar/fruits.txt
352 b/bar/orange/fly/gnat.py
353 b/bar/orange/fly/housefly.txt
354 $ mv oldmf .hg/store/meta/a
355 $ mv oldmf2 .hg/store/meta/b/foo
356
357 Test files for a sub directory, including only a directory within it, and
358 including an unrelated directory.
359
360 $ mv .hg/store/meta/a oldmf
361 $ mv .hg/store/meta/b/foo oldmf2
362 $ hg files -r . -I b/bar/orange -I a b
363 b/bar/orange/fly/gnat.py
364 b/bar/orange/fly/housefly.txt
365 $ mv oldmf .hg/store/meta/a
366 $ mv oldmf2 .hg/store/meta/b/foo
367
368 Test files for a pattern, including a directory, and excluding a directory
369 within that.
370
371 $ mv .hg/store/meta/a oldmf
372 $ mv .hg/store/meta/b/foo oldmf2
373 $ mv .hg/store/meta/b/bar/orange oldmf3
374 $ hg files -r . glob:**.txt -I b/bar -X b/bar/orange
375 b/bar/fruits.txt
376 $ mv oldmf .hg/store/meta/a
377 $ mv oldmf2 .hg/store/meta/b/foo
378 $ mv oldmf3 .hg/store/meta/b/bar/orange
379
General Comments 0
You need to be logged in to leave comments. Login now