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