##// END OF EJS Templates
sparse: apply update with in a `parentchange` context...
marmoute -
r48508:7bdfd882 default
parent child Browse files
Show More
@@ -1,830 +1,838 b''
1 # sparse.py - functionality for sparse checkouts
1 # sparse.py - functionality for sparse checkouts
2 #
2 #
3 # Copyright 2014 Facebook, Inc.
3 # Copyright 2014 Facebook, Inc.
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import hex
13 from .node import hex
14 from . import (
14 from . import (
15 error,
15 error,
16 match as matchmod,
16 match as matchmod,
17 merge as mergemod,
17 merge as mergemod,
18 mergestate as mergestatemod,
18 mergestate as mergestatemod,
19 pathutil,
19 pathutil,
20 pycompat,
20 pycompat,
21 requirements,
21 requirements,
22 scmutil,
22 scmutil,
23 util,
23 util,
24 )
24 )
25 from .utils import hashutil
25 from .utils import hashutil
26
26
27
27
28 # Whether sparse features are enabled. This variable is intended to be
28 # Whether sparse features are enabled. This variable is intended to be
29 # temporary to facilitate porting sparse to core. It should eventually be
29 # temporary to facilitate porting sparse to core. It should eventually be
30 # a per-repo option, possibly a repo requirement.
30 # a per-repo option, possibly a repo requirement.
31 enabled = False
31 enabled = False
32
32
33
33
34 def parseconfig(ui, raw, action):
34 def parseconfig(ui, raw, action):
35 """Parse sparse config file content.
35 """Parse sparse config file content.
36
36
37 action is the command which is trigerring this read, can be narrow, sparse
37 action is the command which is trigerring this read, can be narrow, sparse
38
38
39 Returns a tuple of includes, excludes, and profiles.
39 Returns a tuple of includes, excludes, and profiles.
40 """
40 """
41 includes = set()
41 includes = set()
42 excludes = set()
42 excludes = set()
43 profiles = set()
43 profiles = set()
44 current = None
44 current = None
45 havesection = False
45 havesection = False
46
46
47 for line in raw.split(b'\n'):
47 for line in raw.split(b'\n'):
48 line = line.strip()
48 line = line.strip()
49 if not line or line.startswith(b'#'):
49 if not line or line.startswith(b'#'):
50 # empty or comment line, skip
50 # empty or comment line, skip
51 continue
51 continue
52 elif line.startswith(b'%include '):
52 elif line.startswith(b'%include '):
53 line = line[9:].strip()
53 line = line[9:].strip()
54 if line:
54 if line:
55 profiles.add(line)
55 profiles.add(line)
56 elif line == b'[include]':
56 elif line == b'[include]':
57 if havesection and current != includes:
57 if havesection and current != includes:
58 # TODO pass filename into this API so we can report it.
58 # TODO pass filename into this API so we can report it.
59 raise error.Abort(
59 raise error.Abort(
60 _(
60 _(
61 b'%(action)s config cannot have includes '
61 b'%(action)s config cannot have includes '
62 b'after excludes'
62 b'after excludes'
63 )
63 )
64 % {b'action': action}
64 % {b'action': action}
65 )
65 )
66 havesection = True
66 havesection = True
67 current = includes
67 current = includes
68 continue
68 continue
69 elif line == b'[exclude]':
69 elif line == b'[exclude]':
70 havesection = True
70 havesection = True
71 current = excludes
71 current = excludes
72 elif line:
72 elif line:
73 if current is None:
73 if current is None:
74 raise error.Abort(
74 raise error.Abort(
75 _(
75 _(
76 b'%(action)s config entry outside of '
76 b'%(action)s config entry outside of '
77 b'section: %(line)s'
77 b'section: %(line)s'
78 )
78 )
79 % {b'action': action, b'line': line},
79 % {b'action': action, b'line': line},
80 hint=_(
80 hint=_(
81 b'add an [include] or [exclude] line '
81 b'add an [include] or [exclude] line '
82 b'to declare the entry type'
82 b'to declare the entry type'
83 ),
83 ),
84 )
84 )
85
85
86 if line.strip().startswith(b'/'):
86 if line.strip().startswith(b'/'):
87 ui.warn(
87 ui.warn(
88 _(
88 _(
89 b'warning: %(action)s profile cannot use'
89 b'warning: %(action)s profile cannot use'
90 b' paths starting with /, ignoring %(line)s\n'
90 b' paths starting with /, ignoring %(line)s\n'
91 )
91 )
92 % {b'action': action, b'line': line}
92 % {b'action': action, b'line': line}
93 )
93 )
94 continue
94 continue
95 current.add(line)
95 current.add(line)
96
96
97 return includes, excludes, profiles
97 return includes, excludes, profiles
98
98
99
99
100 # Exists as separate function to facilitate monkeypatching.
100 # Exists as separate function to facilitate monkeypatching.
101 def readprofile(repo, profile, changeid):
101 def readprofile(repo, profile, changeid):
102 """Resolve the raw content of a sparse profile file."""
102 """Resolve the raw content of a sparse profile file."""
103 # TODO add some kind of cache here because this incurs a manifest
103 # TODO add some kind of cache here because this incurs a manifest
104 # resolve and can be slow.
104 # resolve and can be slow.
105 return repo.filectx(profile, changeid=changeid).data()
105 return repo.filectx(profile, changeid=changeid).data()
106
106
107
107
108 def patternsforrev(repo, rev):
108 def patternsforrev(repo, rev):
109 """Obtain sparse checkout patterns for the given rev.
109 """Obtain sparse checkout patterns for the given rev.
110
110
111 Returns a tuple of iterables representing includes, excludes, and
111 Returns a tuple of iterables representing includes, excludes, and
112 patterns.
112 patterns.
113 """
113 """
114 # Feature isn't enabled. No-op.
114 # Feature isn't enabled. No-op.
115 if not enabled:
115 if not enabled:
116 return set(), set(), set()
116 return set(), set(), set()
117
117
118 raw = repo.vfs.tryread(b'sparse')
118 raw = repo.vfs.tryread(b'sparse')
119 if not raw:
119 if not raw:
120 return set(), set(), set()
120 return set(), set(), set()
121
121
122 if rev is None:
122 if rev is None:
123 raise error.Abort(
123 raise error.Abort(
124 _(b'cannot parse sparse patterns from working directory')
124 _(b'cannot parse sparse patterns from working directory')
125 )
125 )
126
126
127 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
127 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
128 ctx = repo[rev]
128 ctx = repo[rev]
129
129
130 if profiles:
130 if profiles:
131 visited = set()
131 visited = set()
132 while profiles:
132 while profiles:
133 profile = profiles.pop()
133 profile = profiles.pop()
134 if profile in visited:
134 if profile in visited:
135 continue
135 continue
136
136
137 visited.add(profile)
137 visited.add(profile)
138
138
139 try:
139 try:
140 raw = readprofile(repo, profile, rev)
140 raw = readprofile(repo, profile, rev)
141 except error.ManifestLookupError:
141 except error.ManifestLookupError:
142 msg = (
142 msg = (
143 b"warning: sparse profile '%s' not found "
143 b"warning: sparse profile '%s' not found "
144 b"in rev %s - ignoring it\n" % (profile, ctx)
144 b"in rev %s - ignoring it\n" % (profile, ctx)
145 )
145 )
146 # experimental config: sparse.missingwarning
146 # experimental config: sparse.missingwarning
147 if repo.ui.configbool(b'sparse', b'missingwarning'):
147 if repo.ui.configbool(b'sparse', b'missingwarning'):
148 repo.ui.warn(msg)
148 repo.ui.warn(msg)
149 else:
149 else:
150 repo.ui.debug(msg)
150 repo.ui.debug(msg)
151 continue
151 continue
152
152
153 pincludes, pexcludes, subprofs = parseconfig(
153 pincludes, pexcludes, subprofs = parseconfig(
154 repo.ui, raw, b'sparse'
154 repo.ui, raw, b'sparse'
155 )
155 )
156 includes.update(pincludes)
156 includes.update(pincludes)
157 excludes.update(pexcludes)
157 excludes.update(pexcludes)
158 profiles.update(subprofs)
158 profiles.update(subprofs)
159
159
160 profiles = visited
160 profiles = visited
161
161
162 if includes:
162 if includes:
163 includes.add(b'.hg*')
163 includes.add(b'.hg*')
164
164
165 return includes, excludes, profiles
165 return includes, excludes, profiles
166
166
167
167
168 def activeconfig(repo):
168 def activeconfig(repo):
169 """Determine the active sparse config rules.
169 """Determine the active sparse config rules.
170
170
171 Rules are constructed by reading the current sparse config and bringing in
171 Rules are constructed by reading the current sparse config and bringing in
172 referenced profiles from parents of the working directory.
172 referenced profiles from parents of the working directory.
173 """
173 """
174 revs = [
174 revs = [
175 repo.changelog.rev(node)
175 repo.changelog.rev(node)
176 for node in repo.dirstate.parents()
176 for node in repo.dirstate.parents()
177 if node != repo.nullid
177 if node != repo.nullid
178 ]
178 ]
179
179
180 allincludes = set()
180 allincludes = set()
181 allexcludes = set()
181 allexcludes = set()
182 allprofiles = set()
182 allprofiles = set()
183
183
184 for rev in revs:
184 for rev in revs:
185 includes, excludes, profiles = patternsforrev(repo, rev)
185 includes, excludes, profiles = patternsforrev(repo, rev)
186 allincludes |= includes
186 allincludes |= includes
187 allexcludes |= excludes
187 allexcludes |= excludes
188 allprofiles |= profiles
188 allprofiles |= profiles
189
189
190 return allincludes, allexcludes, allprofiles
190 return allincludes, allexcludes, allprofiles
191
191
192
192
193 def configsignature(repo, includetemp=True):
193 def configsignature(repo, includetemp=True):
194 """Obtain the signature string for the current sparse configuration.
194 """Obtain the signature string for the current sparse configuration.
195
195
196 This is used to construct a cache key for matchers.
196 This is used to construct a cache key for matchers.
197 """
197 """
198 cache = repo._sparsesignaturecache
198 cache = repo._sparsesignaturecache
199
199
200 signature = cache.get(b'signature')
200 signature = cache.get(b'signature')
201
201
202 if includetemp:
202 if includetemp:
203 tempsignature = cache.get(b'tempsignature')
203 tempsignature = cache.get(b'tempsignature')
204 else:
204 else:
205 tempsignature = b'0'
205 tempsignature = b'0'
206
206
207 if signature is None or (includetemp and tempsignature is None):
207 if signature is None or (includetemp and tempsignature is None):
208 signature = hex(hashutil.sha1(repo.vfs.tryread(b'sparse')).digest())
208 signature = hex(hashutil.sha1(repo.vfs.tryread(b'sparse')).digest())
209 cache[b'signature'] = signature
209 cache[b'signature'] = signature
210
210
211 if includetemp:
211 if includetemp:
212 raw = repo.vfs.tryread(b'tempsparse')
212 raw = repo.vfs.tryread(b'tempsparse')
213 tempsignature = hex(hashutil.sha1(raw).digest())
213 tempsignature = hex(hashutil.sha1(raw).digest())
214 cache[b'tempsignature'] = tempsignature
214 cache[b'tempsignature'] = tempsignature
215
215
216 return b'%s %s' % (signature, tempsignature)
216 return b'%s %s' % (signature, tempsignature)
217
217
218
218
219 def writeconfig(repo, includes, excludes, profiles):
219 def writeconfig(repo, includes, excludes, profiles):
220 """Write the sparse config file given a sparse configuration."""
220 """Write the sparse config file given a sparse configuration."""
221 with repo.vfs(b'sparse', b'wb') as fh:
221 with repo.vfs(b'sparse', b'wb') as fh:
222 for p in sorted(profiles):
222 for p in sorted(profiles):
223 fh.write(b'%%include %s\n' % p)
223 fh.write(b'%%include %s\n' % p)
224
224
225 if includes:
225 if includes:
226 fh.write(b'[include]\n')
226 fh.write(b'[include]\n')
227 for i in sorted(includes):
227 for i in sorted(includes):
228 fh.write(i)
228 fh.write(i)
229 fh.write(b'\n')
229 fh.write(b'\n')
230
230
231 if excludes:
231 if excludes:
232 fh.write(b'[exclude]\n')
232 fh.write(b'[exclude]\n')
233 for e in sorted(excludes):
233 for e in sorted(excludes):
234 fh.write(e)
234 fh.write(e)
235 fh.write(b'\n')
235 fh.write(b'\n')
236
236
237 repo._sparsesignaturecache.clear()
237 repo._sparsesignaturecache.clear()
238
238
239
239
240 def readtemporaryincludes(repo):
240 def readtemporaryincludes(repo):
241 raw = repo.vfs.tryread(b'tempsparse')
241 raw = repo.vfs.tryread(b'tempsparse')
242 if not raw:
242 if not raw:
243 return set()
243 return set()
244
244
245 return set(raw.split(b'\n'))
245 return set(raw.split(b'\n'))
246
246
247
247
248 def writetemporaryincludes(repo, includes):
248 def writetemporaryincludes(repo, includes):
249 repo.vfs.write(b'tempsparse', b'\n'.join(sorted(includes)))
249 repo.vfs.write(b'tempsparse', b'\n'.join(sorted(includes)))
250 repo._sparsesignaturecache.clear()
250 repo._sparsesignaturecache.clear()
251
251
252
252
253 def addtemporaryincludes(repo, additional):
253 def addtemporaryincludes(repo, additional):
254 includes = readtemporaryincludes(repo)
254 includes = readtemporaryincludes(repo)
255 for i in additional:
255 for i in additional:
256 includes.add(i)
256 includes.add(i)
257 writetemporaryincludes(repo, includes)
257 writetemporaryincludes(repo, includes)
258
258
259
259
260 def prunetemporaryincludes(repo):
260 def prunetemporaryincludes(repo):
261 if not enabled or not repo.vfs.exists(b'tempsparse'):
261 if not enabled or not repo.vfs.exists(b'tempsparse'):
262 return
262 return
263
263
264 s = repo.status()
264 s = repo.status()
265 if s.modified or s.added or s.removed or s.deleted:
265 if s.modified or s.added or s.removed or s.deleted:
266 # Still have pending changes. Don't bother trying to prune.
266 # Still have pending changes. Don't bother trying to prune.
267 return
267 return
268
268
269 sparsematch = matcher(repo, includetemp=False)
269 sparsematch = matcher(repo, includetemp=False)
270 dirstate = repo.dirstate
270 dirstate = repo.dirstate
271 mresult = mergemod.mergeresult()
271 mresult = mergemod.mergeresult()
272 dropped = []
272 dropped = []
273 tempincludes = readtemporaryincludes(repo)
273 tempincludes = readtemporaryincludes(repo)
274 for file in tempincludes:
274 for file in tempincludes:
275 if file in dirstate and not sparsematch(file):
275 if file in dirstate and not sparsematch(file):
276 message = _(b'dropping temporarily included sparse files')
276 message = _(b'dropping temporarily included sparse files')
277 mresult.addfile(file, mergestatemod.ACTION_REMOVE, None, message)
277 mresult.addfile(file, mergestatemod.ACTION_REMOVE, None, message)
278 dropped.append(file)
278 dropped.append(file)
279
279
280 mergemod.applyupdates(
280 mergemod.applyupdates(
281 repo, mresult, repo[None], repo[b'.'], False, wantfiledata=False
281 repo, mresult, repo[None], repo[b'.'], False, wantfiledata=False
282 )
282 )
283
283
284 # Fix dirstate
284 # Fix dirstate
285 for file in dropped:
285 for file in dropped:
286 dirstate.drop(file)
286 dirstate.drop(file)
287
287
288 repo.vfs.unlink(b'tempsparse')
288 repo.vfs.unlink(b'tempsparse')
289 repo._sparsesignaturecache.clear()
289 repo._sparsesignaturecache.clear()
290 msg = _(
290 msg = _(
291 b'cleaned up %d temporarily added file(s) from the '
291 b'cleaned up %d temporarily added file(s) from the '
292 b'sparse checkout\n'
292 b'sparse checkout\n'
293 )
293 )
294 repo.ui.status(msg % len(tempincludes))
294 repo.ui.status(msg % len(tempincludes))
295
295
296
296
297 def forceincludematcher(matcher, includes):
297 def forceincludematcher(matcher, includes):
298 """Returns a matcher that returns true for any of the forced includes
298 """Returns a matcher that returns true for any of the forced includes
299 before testing against the actual matcher."""
299 before testing against the actual matcher."""
300 kindpats = [(b'path', include, b'') for include in includes]
300 kindpats = [(b'path', include, b'') for include in includes]
301 includematcher = matchmod.includematcher(b'', kindpats)
301 includematcher = matchmod.includematcher(b'', kindpats)
302 return matchmod.unionmatcher([includematcher, matcher])
302 return matchmod.unionmatcher([includematcher, matcher])
303
303
304
304
305 def matcher(repo, revs=None, includetemp=True):
305 def matcher(repo, revs=None, includetemp=True):
306 """Obtain a matcher for sparse working directories for the given revs.
306 """Obtain a matcher for sparse working directories for the given revs.
307
307
308 If multiple revisions are specified, the matcher is the union of all
308 If multiple revisions are specified, the matcher is the union of all
309 revs.
309 revs.
310
310
311 ``includetemp`` indicates whether to use the temporary sparse profile.
311 ``includetemp`` indicates whether to use the temporary sparse profile.
312 """
312 """
313 # If sparse isn't enabled, sparse matcher matches everything.
313 # If sparse isn't enabled, sparse matcher matches everything.
314 if not enabled:
314 if not enabled:
315 return matchmod.always()
315 return matchmod.always()
316
316
317 if not revs or revs == [None]:
317 if not revs or revs == [None]:
318 revs = [
318 revs = [
319 repo.changelog.rev(node)
319 repo.changelog.rev(node)
320 for node in repo.dirstate.parents()
320 for node in repo.dirstate.parents()
321 if node != repo.nullid
321 if node != repo.nullid
322 ]
322 ]
323
323
324 signature = configsignature(repo, includetemp=includetemp)
324 signature = configsignature(repo, includetemp=includetemp)
325
325
326 key = b'%s %s' % (signature, b' '.join(map(pycompat.bytestr, revs)))
326 key = b'%s %s' % (signature, b' '.join(map(pycompat.bytestr, revs)))
327
327
328 result = repo._sparsematchercache.get(key)
328 result = repo._sparsematchercache.get(key)
329 if result:
329 if result:
330 return result
330 return result
331
331
332 matchers = []
332 matchers = []
333 for rev in revs:
333 for rev in revs:
334 try:
334 try:
335 includes, excludes, profiles = patternsforrev(repo, rev)
335 includes, excludes, profiles = patternsforrev(repo, rev)
336
336
337 if includes or excludes:
337 if includes or excludes:
338 matcher = matchmod.match(
338 matcher = matchmod.match(
339 repo.root,
339 repo.root,
340 b'',
340 b'',
341 [],
341 [],
342 include=includes,
342 include=includes,
343 exclude=excludes,
343 exclude=excludes,
344 default=b'relpath',
344 default=b'relpath',
345 )
345 )
346 matchers.append(matcher)
346 matchers.append(matcher)
347 except IOError:
347 except IOError:
348 pass
348 pass
349
349
350 if not matchers:
350 if not matchers:
351 result = matchmod.always()
351 result = matchmod.always()
352 elif len(matchers) == 1:
352 elif len(matchers) == 1:
353 result = matchers[0]
353 result = matchers[0]
354 else:
354 else:
355 result = matchmod.unionmatcher(matchers)
355 result = matchmod.unionmatcher(matchers)
356
356
357 if includetemp:
357 if includetemp:
358 tempincludes = readtemporaryincludes(repo)
358 tempincludes = readtemporaryincludes(repo)
359 result = forceincludematcher(result, tempincludes)
359 result = forceincludematcher(result, tempincludes)
360
360
361 repo._sparsematchercache[key] = result
361 repo._sparsematchercache[key] = result
362
362
363 return result
363 return result
364
364
365
365
366 def filterupdatesactions(repo, wctx, mctx, branchmerge, mresult):
366 def filterupdatesactions(repo, wctx, mctx, branchmerge, mresult):
367 """Filter updates to only lay out files that match the sparse rules."""
367 """Filter updates to only lay out files that match the sparse rules."""
368 if not enabled:
368 if not enabled:
369 return
369 return
370
370
371 oldrevs = [pctx.rev() for pctx in wctx.parents()]
371 oldrevs = [pctx.rev() for pctx in wctx.parents()]
372 oldsparsematch = matcher(repo, oldrevs)
372 oldsparsematch = matcher(repo, oldrevs)
373
373
374 if oldsparsematch.always():
374 if oldsparsematch.always():
375 return
375 return
376
376
377 files = set()
377 files = set()
378 prunedactions = {}
378 prunedactions = {}
379
379
380 if branchmerge:
380 if branchmerge:
381 # If we're merging, use the wctx filter, since we're merging into
381 # If we're merging, use the wctx filter, since we're merging into
382 # the wctx.
382 # the wctx.
383 sparsematch = matcher(repo, [wctx.p1().rev()])
383 sparsematch = matcher(repo, [wctx.p1().rev()])
384 else:
384 else:
385 # If we're updating, use the target context's filter, since we're
385 # If we're updating, use the target context's filter, since we're
386 # moving to the target context.
386 # moving to the target context.
387 sparsematch = matcher(repo, [mctx.rev()])
387 sparsematch = matcher(repo, [mctx.rev()])
388
388
389 temporaryfiles = []
389 temporaryfiles = []
390 for file, action in mresult.filemap():
390 for file, action in mresult.filemap():
391 type, args, msg = action
391 type, args, msg = action
392 files.add(file)
392 files.add(file)
393 if sparsematch(file):
393 if sparsematch(file):
394 prunedactions[file] = action
394 prunedactions[file] = action
395 elif type == mergestatemod.ACTION_MERGE:
395 elif type == mergestatemod.ACTION_MERGE:
396 temporaryfiles.append(file)
396 temporaryfiles.append(file)
397 prunedactions[file] = action
397 prunedactions[file] = action
398 elif branchmerge:
398 elif branchmerge:
399 if type not in mergestatemod.NO_OP_ACTIONS:
399 if type not in mergestatemod.NO_OP_ACTIONS:
400 temporaryfiles.append(file)
400 temporaryfiles.append(file)
401 prunedactions[file] = action
401 prunedactions[file] = action
402 elif type == mergestatemod.ACTION_FORGET:
402 elif type == mergestatemod.ACTION_FORGET:
403 prunedactions[file] = action
403 prunedactions[file] = action
404 elif file in wctx:
404 elif file in wctx:
405 prunedactions[file] = (mergestatemod.ACTION_REMOVE, args, msg)
405 prunedactions[file] = (mergestatemod.ACTION_REMOVE, args, msg)
406
406
407 # in case or rename on one side, it is possible that f1 might not
407 # in case or rename on one side, it is possible that f1 might not
408 # be present in sparse checkout we should include it
408 # be present in sparse checkout we should include it
409 # TODO: should we do the same for f2?
409 # TODO: should we do the same for f2?
410 # exists as a separate check because file can be in sparse and hence
410 # exists as a separate check because file can be in sparse and hence
411 # if we try to club this condition in above `elif type == ACTION_MERGE`
411 # if we try to club this condition in above `elif type == ACTION_MERGE`
412 # it won't be triggered
412 # it won't be triggered
413 if branchmerge and type == mergestatemod.ACTION_MERGE:
413 if branchmerge and type == mergestatemod.ACTION_MERGE:
414 f1, f2, fa, move, anc = args
414 f1, f2, fa, move, anc = args
415 if not sparsematch(f1):
415 if not sparsematch(f1):
416 temporaryfiles.append(f1)
416 temporaryfiles.append(f1)
417
417
418 if len(temporaryfiles) > 0:
418 if len(temporaryfiles) > 0:
419 repo.ui.status(
419 repo.ui.status(
420 _(
420 _(
421 b'temporarily included %d file(s) in the sparse '
421 b'temporarily included %d file(s) in the sparse '
422 b'checkout for merging\n'
422 b'checkout for merging\n'
423 )
423 )
424 % len(temporaryfiles)
424 % len(temporaryfiles)
425 )
425 )
426 addtemporaryincludes(repo, temporaryfiles)
426 addtemporaryincludes(repo, temporaryfiles)
427
427
428 # Add the new files to the working copy so they can be merged, etc
428 # Add the new files to the working copy so they can be merged, etc
429 tmresult = mergemod.mergeresult()
429 tmresult = mergemod.mergeresult()
430 message = b'temporarily adding to sparse checkout'
430 message = b'temporarily adding to sparse checkout'
431 wctxmanifest = repo[None].manifest()
431 wctxmanifest = repo[None].manifest()
432 for file in temporaryfiles:
432 for file in temporaryfiles:
433 if file in wctxmanifest:
433 if file in wctxmanifest:
434 fctx = repo[None][file]
434 fctx = repo[None][file]
435 tmresult.addfile(
435 tmresult.addfile(
436 file,
436 file,
437 mergestatemod.ACTION_GET,
437 mergestatemod.ACTION_GET,
438 (fctx.flags(), False),
438 (fctx.flags(), False),
439 message,
439 message,
440 )
440 )
441
441
442 mergemod.applyupdates(
442 with repo.dirstate.parentchange():
443 repo, tmresult, repo[None], repo[b'.'], False, wantfiledata=False
443 mergemod.applyupdates(
444 )
444 repo,
445 tmresult,
446 repo[None],
447 repo[b'.'],
448 False,
449 wantfiledata=False,
450 )
445
451
446 dirstate = repo.dirstate
452 dirstate = repo.dirstate
447 for file, flags, msg in tmresult.getactions([mergestatemod.ACTION_GET]):
453 for file, flags, msg in tmresult.getactions(
448 dirstate.normal(file)
454 [mergestatemod.ACTION_GET]
455 ):
456 dirstate.normal(file)
449
457
450 profiles = activeconfig(repo)[2]
458 profiles = activeconfig(repo)[2]
451 changedprofiles = profiles & files
459 changedprofiles = profiles & files
452 # If an active profile changed during the update, refresh the checkout.
460 # If an active profile changed during the update, refresh the checkout.
453 # Don't do this during a branch merge, since all incoming changes should
461 # Don't do this during a branch merge, since all incoming changes should
454 # have been handled by the temporary includes above.
462 # have been handled by the temporary includes above.
455 if changedprofiles and not branchmerge:
463 if changedprofiles and not branchmerge:
456 mf = mctx.manifest()
464 mf = mctx.manifest()
457 for file in mf:
465 for file in mf:
458 old = oldsparsematch(file)
466 old = oldsparsematch(file)
459 new = sparsematch(file)
467 new = sparsematch(file)
460 if not old and new:
468 if not old and new:
461 flags = mf.flags(file)
469 flags = mf.flags(file)
462 prunedactions[file] = (
470 prunedactions[file] = (
463 mergestatemod.ACTION_GET,
471 mergestatemod.ACTION_GET,
464 (flags, False),
472 (flags, False),
465 b'',
473 b'',
466 )
474 )
467 elif old and not new:
475 elif old and not new:
468 prunedactions[file] = (mergestatemod.ACTION_REMOVE, [], b'')
476 prunedactions[file] = (mergestatemod.ACTION_REMOVE, [], b'')
469
477
470 mresult.setactions(prunedactions)
478 mresult.setactions(prunedactions)
471
479
472
480
473 def refreshwdir(repo, origstatus, origsparsematch, force=False):
481 def refreshwdir(repo, origstatus, origsparsematch, force=False):
474 """Refreshes working directory by taking sparse config into account.
482 """Refreshes working directory by taking sparse config into account.
475
483
476 The old status and sparse matcher is compared against the current sparse
484 The old status and sparse matcher is compared against the current sparse
477 matcher.
485 matcher.
478
486
479 Will abort if a file with pending changes is being excluded or included
487 Will abort if a file with pending changes is being excluded or included
480 unless ``force`` is True.
488 unless ``force`` is True.
481 """
489 """
482 # Verify there are no pending changes
490 # Verify there are no pending changes
483 pending = set()
491 pending = set()
484 pending.update(origstatus.modified)
492 pending.update(origstatus.modified)
485 pending.update(origstatus.added)
493 pending.update(origstatus.added)
486 pending.update(origstatus.removed)
494 pending.update(origstatus.removed)
487 sparsematch = matcher(repo)
495 sparsematch = matcher(repo)
488 abort = False
496 abort = False
489
497
490 for f in pending:
498 for f in pending:
491 if not sparsematch(f):
499 if not sparsematch(f):
492 repo.ui.warn(_(b"pending changes to '%s'\n") % f)
500 repo.ui.warn(_(b"pending changes to '%s'\n") % f)
493 abort = not force
501 abort = not force
494
502
495 if abort:
503 if abort:
496 raise error.Abort(
504 raise error.Abort(
497 _(b'could not update sparseness due to pending changes')
505 _(b'could not update sparseness due to pending changes')
498 )
506 )
499
507
500 # Calculate merge result
508 # Calculate merge result
501 dirstate = repo.dirstate
509 dirstate = repo.dirstate
502 ctx = repo[b'.']
510 ctx = repo[b'.']
503 added = []
511 added = []
504 lookup = []
512 lookup = []
505 dropped = []
513 dropped = []
506 mf = ctx.manifest()
514 mf = ctx.manifest()
507 files = set(mf)
515 files = set(mf)
508 mresult = mergemod.mergeresult()
516 mresult = mergemod.mergeresult()
509
517
510 for file in files:
518 for file in files:
511 old = origsparsematch(file)
519 old = origsparsematch(file)
512 new = sparsematch(file)
520 new = sparsematch(file)
513 # Add files that are newly included, or that don't exist in
521 # Add files that are newly included, or that don't exist in
514 # the dirstate yet.
522 # the dirstate yet.
515 if (new and not old) or (old and new and not file in dirstate):
523 if (new and not old) or (old and new and not file in dirstate):
516 fl = mf.flags(file)
524 fl = mf.flags(file)
517 if repo.wvfs.exists(file):
525 if repo.wvfs.exists(file):
518 mresult.addfile(file, mergestatemod.ACTION_EXEC, (fl,), b'')
526 mresult.addfile(file, mergestatemod.ACTION_EXEC, (fl,), b'')
519 lookup.append(file)
527 lookup.append(file)
520 else:
528 else:
521 mresult.addfile(
529 mresult.addfile(
522 file, mergestatemod.ACTION_GET, (fl, False), b''
530 file, mergestatemod.ACTION_GET, (fl, False), b''
523 )
531 )
524 added.append(file)
532 added.append(file)
525 # Drop files that are newly excluded, or that still exist in
533 # Drop files that are newly excluded, or that still exist in
526 # the dirstate.
534 # the dirstate.
527 elif (old and not new) or (not old and not new and file in dirstate):
535 elif (old and not new) or (not old and not new and file in dirstate):
528 dropped.append(file)
536 dropped.append(file)
529 if file not in pending:
537 if file not in pending:
530 mresult.addfile(file, mergestatemod.ACTION_REMOVE, [], b'')
538 mresult.addfile(file, mergestatemod.ACTION_REMOVE, [], b'')
531
539
532 # Verify there are no pending changes in newly included files
540 # Verify there are no pending changes in newly included files
533 abort = False
541 abort = False
534 for file in lookup:
542 for file in lookup:
535 repo.ui.warn(_(b"pending changes to '%s'\n") % file)
543 repo.ui.warn(_(b"pending changes to '%s'\n") % file)
536 abort = not force
544 abort = not force
537 if abort:
545 if abort:
538 raise error.Abort(
546 raise error.Abort(
539 _(
547 _(
540 b'cannot change sparseness due to pending '
548 b'cannot change sparseness due to pending '
541 b'changes (delete the files or use '
549 b'changes (delete the files or use '
542 b'--force to bring them back dirty)'
550 b'--force to bring them back dirty)'
543 )
551 )
544 )
552 )
545
553
546 # Check for files that were only in the dirstate.
554 # Check for files that were only in the dirstate.
547 for file, state in pycompat.iteritems(dirstate):
555 for file, state in pycompat.iteritems(dirstate):
548 if not file in files:
556 if not file in files:
549 old = origsparsematch(file)
557 old = origsparsematch(file)
550 new = sparsematch(file)
558 new = sparsematch(file)
551 if old and not new:
559 if old and not new:
552 dropped.append(file)
560 dropped.append(file)
553
561
554 mergemod.applyupdates(
562 mergemod.applyupdates(
555 repo, mresult, repo[None], repo[b'.'], False, wantfiledata=False
563 repo, mresult, repo[None], repo[b'.'], False, wantfiledata=False
556 )
564 )
557
565
558 # Fix dirstate
566 # Fix dirstate
559 for file in added:
567 for file in added:
560 dirstate.normal(file)
568 dirstate.normal(file)
561
569
562 for file in dropped:
570 for file in dropped:
563 dirstate.drop(file)
571 dirstate.drop(file)
564
572
565 for file in lookup:
573 for file in lookup:
566 # File exists on disk, and we're bringing it back in an unknown state.
574 # File exists on disk, and we're bringing it back in an unknown state.
567 dirstate.normallookup(file)
575 dirstate.normallookup(file)
568
576
569 return added, dropped, lookup
577 return added, dropped, lookup
570
578
571
579
572 def aftercommit(repo, node):
580 def aftercommit(repo, node):
573 """Perform actions after a working directory commit."""
581 """Perform actions after a working directory commit."""
574 # This function is called unconditionally, even if sparse isn't
582 # This function is called unconditionally, even if sparse isn't
575 # enabled.
583 # enabled.
576 ctx = repo[node]
584 ctx = repo[node]
577
585
578 profiles = patternsforrev(repo, ctx.rev())[2]
586 profiles = patternsforrev(repo, ctx.rev())[2]
579
587
580 # profiles will only have data if sparse is enabled.
588 # profiles will only have data if sparse is enabled.
581 if profiles & set(ctx.files()):
589 if profiles & set(ctx.files()):
582 origstatus = repo.status()
590 origstatus = repo.status()
583 origsparsematch = matcher(repo)
591 origsparsematch = matcher(repo)
584 refreshwdir(repo, origstatus, origsparsematch, force=True)
592 refreshwdir(repo, origstatus, origsparsematch, force=True)
585
593
586 prunetemporaryincludes(repo)
594 prunetemporaryincludes(repo)
587
595
588
596
589 def _updateconfigandrefreshwdir(
597 def _updateconfigandrefreshwdir(
590 repo, includes, excludes, profiles, force=False, removing=False
598 repo, includes, excludes, profiles, force=False, removing=False
591 ):
599 ):
592 """Update the sparse config and working directory state."""
600 """Update the sparse config and working directory state."""
593 raw = repo.vfs.tryread(b'sparse')
601 raw = repo.vfs.tryread(b'sparse')
594 oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw, b'sparse')
602 oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw, b'sparse')
595
603
596 oldstatus = repo.status()
604 oldstatus = repo.status()
597 oldmatch = matcher(repo)
605 oldmatch = matcher(repo)
598 oldrequires = set(repo.requirements)
606 oldrequires = set(repo.requirements)
599
607
600 # TODO remove this try..except once the matcher integrates better
608 # TODO remove this try..except once the matcher integrates better
601 # with dirstate. We currently have to write the updated config
609 # with dirstate. We currently have to write the updated config
602 # because that will invalidate the matcher cache and force a
610 # because that will invalidate the matcher cache and force a
603 # re-read. We ideally want to update the cached matcher on the
611 # re-read. We ideally want to update the cached matcher on the
604 # repo instance then flush the new config to disk once wdir is
612 # repo instance then flush the new config to disk once wdir is
605 # updated. But this requires massive rework to matcher() and its
613 # updated. But this requires massive rework to matcher() and its
606 # consumers.
614 # consumers.
607
615
608 if requirements.SPARSE_REQUIREMENT in oldrequires and removing:
616 if requirements.SPARSE_REQUIREMENT in oldrequires and removing:
609 repo.requirements.discard(requirements.SPARSE_REQUIREMENT)
617 repo.requirements.discard(requirements.SPARSE_REQUIREMENT)
610 scmutil.writereporequirements(repo)
618 scmutil.writereporequirements(repo)
611 elif requirements.SPARSE_REQUIREMENT not in oldrequires:
619 elif requirements.SPARSE_REQUIREMENT not in oldrequires:
612 repo.requirements.add(requirements.SPARSE_REQUIREMENT)
620 repo.requirements.add(requirements.SPARSE_REQUIREMENT)
613 scmutil.writereporequirements(repo)
621 scmutil.writereporequirements(repo)
614
622
615 try:
623 try:
616 writeconfig(repo, includes, excludes, profiles)
624 writeconfig(repo, includes, excludes, profiles)
617 return refreshwdir(repo, oldstatus, oldmatch, force=force)
625 return refreshwdir(repo, oldstatus, oldmatch, force=force)
618 except Exception:
626 except Exception:
619 if repo.requirements != oldrequires:
627 if repo.requirements != oldrequires:
620 repo.requirements.clear()
628 repo.requirements.clear()
621 repo.requirements |= oldrequires
629 repo.requirements |= oldrequires
622 scmutil.writereporequirements(repo)
630 scmutil.writereporequirements(repo)
623 writeconfig(repo, oldincludes, oldexcludes, oldprofiles)
631 writeconfig(repo, oldincludes, oldexcludes, oldprofiles)
624 raise
632 raise
625
633
626
634
627 def clearrules(repo, force=False):
635 def clearrules(repo, force=False):
628 """Clears include/exclude rules from the sparse config.
636 """Clears include/exclude rules from the sparse config.
629
637
630 The remaining sparse config only has profiles, if defined. The working
638 The remaining sparse config only has profiles, if defined. The working
631 directory is refreshed, as needed.
639 directory is refreshed, as needed.
632 """
640 """
633 with repo.wlock(), repo.dirstate.parentchange():
641 with repo.wlock(), repo.dirstate.parentchange():
634 raw = repo.vfs.tryread(b'sparse')
642 raw = repo.vfs.tryread(b'sparse')
635 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
643 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
636
644
637 if not includes and not excludes:
645 if not includes and not excludes:
638 return
646 return
639
647
640 _updateconfigandrefreshwdir(repo, set(), set(), profiles, force=force)
648 _updateconfigandrefreshwdir(repo, set(), set(), profiles, force=force)
641
649
642
650
643 def importfromfiles(repo, opts, paths, force=False):
651 def importfromfiles(repo, opts, paths, force=False):
644 """Import sparse config rules from files.
652 """Import sparse config rules from files.
645
653
646 The updated sparse config is written out and the working directory
654 The updated sparse config is written out and the working directory
647 is refreshed, as needed.
655 is refreshed, as needed.
648 """
656 """
649 with repo.wlock(), repo.dirstate.parentchange():
657 with repo.wlock(), repo.dirstate.parentchange():
650 # read current configuration
658 # read current configuration
651 raw = repo.vfs.tryread(b'sparse')
659 raw = repo.vfs.tryread(b'sparse')
652 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
660 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
653 aincludes, aexcludes, aprofiles = activeconfig(repo)
661 aincludes, aexcludes, aprofiles = activeconfig(repo)
654
662
655 # Import rules on top; only take in rules that are not yet
663 # Import rules on top; only take in rules that are not yet
656 # part of the active rules.
664 # part of the active rules.
657 changed = False
665 changed = False
658 for p in paths:
666 for p in paths:
659 with util.posixfile(util.expandpath(p), mode=b'rb') as fh:
667 with util.posixfile(util.expandpath(p), mode=b'rb') as fh:
660 raw = fh.read()
668 raw = fh.read()
661
669
662 iincludes, iexcludes, iprofiles = parseconfig(
670 iincludes, iexcludes, iprofiles = parseconfig(
663 repo.ui, raw, b'sparse'
671 repo.ui, raw, b'sparse'
664 )
672 )
665 oldsize = len(includes) + len(excludes) + len(profiles)
673 oldsize = len(includes) + len(excludes) + len(profiles)
666 includes.update(iincludes - aincludes)
674 includes.update(iincludes - aincludes)
667 excludes.update(iexcludes - aexcludes)
675 excludes.update(iexcludes - aexcludes)
668 profiles.update(iprofiles - aprofiles)
676 profiles.update(iprofiles - aprofiles)
669 if len(includes) + len(excludes) + len(profiles) > oldsize:
677 if len(includes) + len(excludes) + len(profiles) > oldsize:
670 changed = True
678 changed = True
671
679
672 profilecount = includecount = excludecount = 0
680 profilecount = includecount = excludecount = 0
673 fcounts = (0, 0, 0)
681 fcounts = (0, 0, 0)
674
682
675 if changed:
683 if changed:
676 profilecount = len(profiles - aprofiles)
684 profilecount = len(profiles - aprofiles)
677 includecount = len(includes - aincludes)
685 includecount = len(includes - aincludes)
678 excludecount = len(excludes - aexcludes)
686 excludecount = len(excludes - aexcludes)
679
687
680 fcounts = map(
688 fcounts = map(
681 len,
689 len,
682 _updateconfigandrefreshwdir(
690 _updateconfigandrefreshwdir(
683 repo, includes, excludes, profiles, force=force
691 repo, includes, excludes, profiles, force=force
684 ),
692 ),
685 )
693 )
686
694
687 printchanges(
695 printchanges(
688 repo.ui, opts, profilecount, includecount, excludecount, *fcounts
696 repo.ui, opts, profilecount, includecount, excludecount, *fcounts
689 )
697 )
690
698
691
699
692 def updateconfig(
700 def updateconfig(
693 repo,
701 repo,
694 pats,
702 pats,
695 opts,
703 opts,
696 include=False,
704 include=False,
697 exclude=False,
705 exclude=False,
698 reset=False,
706 reset=False,
699 delete=False,
707 delete=False,
700 enableprofile=False,
708 enableprofile=False,
701 disableprofile=False,
709 disableprofile=False,
702 force=False,
710 force=False,
703 usereporootpaths=False,
711 usereporootpaths=False,
704 ):
712 ):
705 """Perform a sparse config update.
713 """Perform a sparse config update.
706
714
707 Only one of the actions may be performed.
715 Only one of the actions may be performed.
708
716
709 The new config is written out and a working directory refresh is performed.
717 The new config is written out and a working directory refresh is performed.
710 """
718 """
711 with repo.wlock(), repo.dirstate.parentchange():
719 with repo.wlock(), repo.dirstate.parentchange():
712 raw = repo.vfs.tryread(b'sparse')
720 raw = repo.vfs.tryread(b'sparse')
713 oldinclude, oldexclude, oldprofiles = parseconfig(
721 oldinclude, oldexclude, oldprofiles = parseconfig(
714 repo.ui, raw, b'sparse'
722 repo.ui, raw, b'sparse'
715 )
723 )
716
724
717 if reset:
725 if reset:
718 newinclude = set()
726 newinclude = set()
719 newexclude = set()
727 newexclude = set()
720 newprofiles = set()
728 newprofiles = set()
721 else:
729 else:
722 newinclude = set(oldinclude)
730 newinclude = set(oldinclude)
723 newexclude = set(oldexclude)
731 newexclude = set(oldexclude)
724 newprofiles = set(oldprofiles)
732 newprofiles = set(oldprofiles)
725
733
726 if any(os.path.isabs(pat) for pat in pats):
734 if any(os.path.isabs(pat) for pat in pats):
727 raise error.Abort(_(b'paths cannot be absolute'))
735 raise error.Abort(_(b'paths cannot be absolute'))
728
736
729 if not usereporootpaths:
737 if not usereporootpaths:
730 # let's treat paths as relative to cwd
738 # let's treat paths as relative to cwd
731 root, cwd = repo.root, repo.getcwd()
739 root, cwd = repo.root, repo.getcwd()
732 abspats = []
740 abspats = []
733 for kindpat in pats:
741 for kindpat in pats:
734 kind, pat = matchmod._patsplit(kindpat, None)
742 kind, pat = matchmod._patsplit(kindpat, None)
735 if kind in matchmod.cwdrelativepatternkinds or kind is None:
743 if kind in matchmod.cwdrelativepatternkinds or kind is None:
736 ap = (kind + b':' if kind else b'') + pathutil.canonpath(
744 ap = (kind + b':' if kind else b'') + pathutil.canonpath(
737 root, cwd, pat
745 root, cwd, pat
738 )
746 )
739 abspats.append(ap)
747 abspats.append(ap)
740 else:
748 else:
741 abspats.append(kindpat)
749 abspats.append(kindpat)
742 pats = abspats
750 pats = abspats
743
751
744 if include:
752 if include:
745 newinclude.update(pats)
753 newinclude.update(pats)
746 elif exclude:
754 elif exclude:
747 newexclude.update(pats)
755 newexclude.update(pats)
748 elif enableprofile:
756 elif enableprofile:
749 newprofiles.update(pats)
757 newprofiles.update(pats)
750 elif disableprofile:
758 elif disableprofile:
751 newprofiles.difference_update(pats)
759 newprofiles.difference_update(pats)
752 elif delete:
760 elif delete:
753 newinclude.difference_update(pats)
761 newinclude.difference_update(pats)
754 newexclude.difference_update(pats)
762 newexclude.difference_update(pats)
755
763
756 profilecount = len(newprofiles - oldprofiles) - len(
764 profilecount = len(newprofiles - oldprofiles) - len(
757 oldprofiles - newprofiles
765 oldprofiles - newprofiles
758 )
766 )
759 includecount = len(newinclude - oldinclude) - len(
767 includecount = len(newinclude - oldinclude) - len(
760 oldinclude - newinclude
768 oldinclude - newinclude
761 )
769 )
762 excludecount = len(newexclude - oldexclude) - len(
770 excludecount = len(newexclude - oldexclude) - len(
763 oldexclude - newexclude
771 oldexclude - newexclude
764 )
772 )
765
773
766 fcounts = map(
774 fcounts = map(
767 len,
775 len,
768 _updateconfigandrefreshwdir(
776 _updateconfigandrefreshwdir(
769 repo,
777 repo,
770 newinclude,
778 newinclude,
771 newexclude,
779 newexclude,
772 newprofiles,
780 newprofiles,
773 force=force,
781 force=force,
774 removing=reset,
782 removing=reset,
775 ),
783 ),
776 )
784 )
777
785
778 printchanges(
786 printchanges(
779 repo.ui, opts, profilecount, includecount, excludecount, *fcounts
787 repo.ui, opts, profilecount, includecount, excludecount, *fcounts
780 )
788 )
781
789
782
790
783 def printchanges(
791 def printchanges(
784 ui,
792 ui,
785 opts,
793 opts,
786 profilecount=0,
794 profilecount=0,
787 includecount=0,
795 includecount=0,
788 excludecount=0,
796 excludecount=0,
789 added=0,
797 added=0,
790 dropped=0,
798 dropped=0,
791 conflicting=0,
799 conflicting=0,
792 ):
800 ):
793 """Print output summarizing sparse config changes."""
801 """Print output summarizing sparse config changes."""
794 with ui.formatter(b'sparse', opts) as fm:
802 with ui.formatter(b'sparse', opts) as fm:
795 fm.startitem()
803 fm.startitem()
796 fm.condwrite(
804 fm.condwrite(
797 ui.verbose,
805 ui.verbose,
798 b'profiles_added',
806 b'profiles_added',
799 _(b'Profiles changed: %d\n'),
807 _(b'Profiles changed: %d\n'),
800 profilecount,
808 profilecount,
801 )
809 )
802 fm.condwrite(
810 fm.condwrite(
803 ui.verbose,
811 ui.verbose,
804 b'include_rules_added',
812 b'include_rules_added',
805 _(b'Include rules changed: %d\n'),
813 _(b'Include rules changed: %d\n'),
806 includecount,
814 includecount,
807 )
815 )
808 fm.condwrite(
816 fm.condwrite(
809 ui.verbose,
817 ui.verbose,
810 b'exclude_rules_added',
818 b'exclude_rules_added',
811 _(b'Exclude rules changed: %d\n'),
819 _(b'Exclude rules changed: %d\n'),
812 excludecount,
820 excludecount,
813 )
821 )
814
822
815 # In 'plain' verbose mode, mergemod.applyupdates already outputs what
823 # In 'plain' verbose mode, mergemod.applyupdates already outputs what
816 # files are added or removed outside of the templating formatter
824 # files are added or removed outside of the templating formatter
817 # framework. No point in repeating ourselves in that case.
825 # framework. No point in repeating ourselves in that case.
818 if not fm.isplain():
826 if not fm.isplain():
819 fm.condwrite(
827 fm.condwrite(
820 ui.verbose, b'files_added', _(b'Files added: %d\n'), added
828 ui.verbose, b'files_added', _(b'Files added: %d\n'), added
821 )
829 )
822 fm.condwrite(
830 fm.condwrite(
823 ui.verbose, b'files_dropped', _(b'Files dropped: %d\n'), dropped
831 ui.verbose, b'files_dropped', _(b'Files dropped: %d\n'), dropped
824 )
832 )
825 fm.condwrite(
833 fm.condwrite(
826 ui.verbose,
834 ui.verbose,
827 b'files_conflicting',
835 b'files_conflicting',
828 _(b'Files conflicting: %d\n'),
836 _(b'Files conflicting: %d\n'),
829 conflicting,
837 conflicting,
830 )
838 )
@@ -1,431 +1,431 b''
1 test sparse
1 test sparse
2
2
3 $ hg init myrepo
3 $ hg init myrepo
4 $ cd myrepo
4 $ cd myrepo
5 $ cat > .hg/hgrc <<EOF
5 $ cat > .hg/hgrc <<EOF
6 > [extensions]
6 > [extensions]
7 > sparse=
7 > sparse=
8 > strip=
8 > strip=
9 > EOF
9 > EOF
10
10
11 $ echo a > show
11 $ echo a > show
12 $ echo x > hide
12 $ echo x > hide
13 $ hg ci -Aqm 'initial'
13 $ hg ci -Aqm 'initial'
14
14
15 $ echo b > show
15 $ echo b > show
16 $ echo y > hide
16 $ echo y > hide
17 $ echo aa > show2
17 $ echo aa > show2
18 $ echo xx > hide2
18 $ echo xx > hide2
19 $ hg ci -Aqm 'two'
19 $ hg ci -Aqm 'two'
20
20
21 Verify basic --include
21 Verify basic --include
22
22
23 $ hg up -q 0
23 $ hg up -q 0
24 $ hg debugsparse --include 'hide'
24 $ hg debugsparse --include 'hide'
25 $ ls -A
25 $ ls -A
26 .hg
26 .hg
27 hide
27 hide
28
28
29 Absolute paths outside the repo should just be rejected
29 Absolute paths outside the repo should just be rejected
30
30
31 #if no-windows
31 #if no-windows
32 $ hg debugsparse --include /foo/bar
32 $ hg debugsparse --include /foo/bar
33 abort: paths cannot be absolute
33 abort: paths cannot be absolute
34 [255]
34 [255]
35 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
35 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
36
36
37 $ hg debugsparse --include '/root'
37 $ hg debugsparse --include '/root'
38 abort: paths cannot be absolute
38 abort: paths cannot be absolute
39 [255]
39 [255]
40 #else
40 #else
41 TODO: See if this can be made to fail the same way as on Unix
41 TODO: See if this can be made to fail the same way as on Unix
42 $ hg debugsparse --include /c/foo/bar
42 $ hg debugsparse --include /c/foo/bar
43 abort: paths cannot be absolute
43 abort: paths cannot be absolute
44 [255]
44 [255]
45 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
45 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
46
46
47 $ hg debugsparse --include '/c/root'
47 $ hg debugsparse --include '/c/root'
48 abort: paths cannot be absolute
48 abort: paths cannot be absolute
49 [255]
49 [255]
50 #endif
50 #endif
51
51
52 Paths should be treated as cwd-relative, not repo-root-relative
52 Paths should be treated as cwd-relative, not repo-root-relative
53 $ mkdir subdir && cd subdir
53 $ mkdir subdir && cd subdir
54 $ hg debugsparse --include path
54 $ hg debugsparse --include path
55 $ hg debugsparse
55 $ hg debugsparse
56 [include]
56 [include]
57 $TESTTMP/myrepo/hide
57 $TESTTMP/myrepo/hide
58 hide
58 hide
59 subdir/path
59 subdir/path
60
60
61 $ cd ..
61 $ cd ..
62 $ echo hello > subdir/file2.ext
62 $ echo hello > subdir/file2.ext
63 $ cd subdir
63 $ cd subdir
64 $ hg debugsparse --include '**.ext' # let us test globs
64 $ hg debugsparse --include '**.ext' # let us test globs
65 $ hg debugsparse --include 'path:abspath' # and a path: pattern
65 $ hg debugsparse --include 'path:abspath' # and a path: pattern
66 $ cd ..
66 $ cd ..
67 $ hg debugsparse
67 $ hg debugsparse
68 [include]
68 [include]
69 $TESTTMP/myrepo/hide
69 $TESTTMP/myrepo/hide
70 hide
70 hide
71 path:abspath
71 path:abspath
72 subdir/**.ext
72 subdir/**.ext
73 subdir/path
73 subdir/path
74
74
75 $ rm -rf subdir
75 $ rm -rf subdir
76
76
77 Verify commiting while sparse includes other files
77 Verify commiting while sparse includes other files
78
78
79 $ echo z > hide
79 $ echo z > hide
80 $ hg ci -Aqm 'edit hide'
80 $ hg ci -Aqm 'edit hide'
81 $ ls -A
81 $ ls -A
82 .hg
82 .hg
83 hide
83 hide
84 $ hg manifest
84 $ hg manifest
85 hide
85 hide
86 show
86 show
87
87
88 Verify --reset brings files back
88 Verify --reset brings files back
89
89
90 $ hg debugsparse --reset
90 $ hg debugsparse --reset
91 $ ls -A
91 $ ls -A
92 .hg
92 .hg
93 hide
93 hide
94 show
94 show
95 $ cat hide
95 $ cat hide
96 z
96 z
97 $ cat show
97 $ cat show
98 a
98 a
99
99
100 Verify 'hg debugsparse' default output
100 Verify 'hg debugsparse' default output
101
101
102 $ hg up -q null
102 $ hg up -q null
103 $ hg debugsparse --include 'show*'
103 $ hg debugsparse --include 'show*'
104
104
105 $ hg debugsparse
105 $ hg debugsparse
106 [include]
106 [include]
107 show*
107 show*
108
108
109 Verify update only writes included files
109 Verify update only writes included files
110
110
111 $ hg up -q 0
111 $ hg up -q 0
112 $ ls -A
112 $ ls -A
113 .hg
113 .hg
114 show
114 show
115
115
116 $ hg up -q 1
116 $ hg up -q 1
117 $ ls -A
117 $ ls -A
118 .hg
118 .hg
119 show
119 show
120 show2
120 show2
121
121
122 Verify status only shows included files
122 Verify status only shows included files
123
123
124 $ touch hide
124 $ touch hide
125 $ touch hide3
125 $ touch hide3
126 $ echo c > show
126 $ echo c > show
127 $ hg status
127 $ hg status
128 M show
128 M show
129
129
130 Adding an excluded file should fail
130 Adding an excluded file should fail
131
131
132 $ hg add hide3
132 $ hg add hide3
133 abort: cannot add 'hide3' - it is outside the sparse checkout
133 abort: cannot add 'hide3' - it is outside the sparse checkout
134 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
134 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
135 [255]
135 [255]
136
136
137 But adding a truly excluded file shouldn't count
137 But adding a truly excluded file shouldn't count
138
138
139 $ hg add hide3 -X hide3
139 $ hg add hide3 -X hide3
140
140
141 Verify deleting sparseness while a file has changes fails
141 Verify deleting sparseness while a file has changes fails
142
142
143 $ hg debugsparse --delete 'show*'
143 $ hg debugsparse --delete 'show*'
144 pending changes to 'hide'
144 pending changes to 'hide'
145 abort: cannot change sparseness due to pending changes (delete the files or use --force to bring them back dirty)
145 abort: cannot change sparseness due to pending changes (delete the files or use --force to bring them back dirty)
146 [255]
146 [255]
147
147
148 Verify deleting sparseness with --force brings back files
148 Verify deleting sparseness with --force brings back files
149
149
150 $ hg debugsparse --delete -f 'show*'
150 $ hg debugsparse --delete -f 'show*'
151 pending changes to 'hide'
151 pending changes to 'hide'
152 $ ls -A
152 $ ls -A
153 .hg
153 .hg
154 hide
154 hide
155 hide2
155 hide2
156 hide3
156 hide3
157 show
157 show
158 show2
158 show2
159 $ hg st
159 $ hg st
160 M hide
160 M hide
161 M show
161 M show
162 ? hide3
162 ? hide3
163
163
164 Verify editing sparseness fails if pending changes
164 Verify editing sparseness fails if pending changes
165
165
166 $ hg debugsparse --include 'show*'
166 $ hg debugsparse --include 'show*'
167 pending changes to 'hide'
167 pending changes to 'hide'
168 abort: could not update sparseness due to pending changes
168 abort: could not update sparseness due to pending changes
169 [255]
169 [255]
170
170
171 Verify adding sparseness hides files
171 Verify adding sparseness hides files
172
172
173 $ hg debugsparse --exclude -f 'hide*'
173 $ hg debugsparse --exclude -f 'hide*'
174 pending changes to 'hide'
174 pending changes to 'hide'
175 $ ls -A
175 $ ls -A
176 .hg
176 .hg
177 hide
177 hide
178 hide3
178 hide3
179 show
179 show
180 show2
180 show2
181 $ hg st
181 $ hg st
182 M show
182 M show
183
183
184 $ hg up -qC .
184 $ hg up -qC .
185 TODO: add an option to purge to also purge files outside the sparse config?
185 TODO: add an option to purge to also purge files outside the sparse config?
186 $ hg purge --all --config extensions.purge=
186 $ hg purge --all --config extensions.purge=
187 $ ls -A
187 $ ls -A
188 .hg
188 .hg
189 hide
189 hide
190 hide3
190 hide3
191 show
191 show
192 show2
192 show2
193 For now, manually remove the files
193 For now, manually remove the files
194 $ rm hide hide3
194 $ rm hide hide3
195
195
196 Verify rebase temporarily includes excluded files
196 Verify rebase temporarily includes excluded files
197
197
198 $ hg rebase -d 1 -r 2 --config extensions.rebase=
198 $ hg rebase -d 1 -r 2 --config extensions.rebase=
199 rebasing 2:b91df4f39e75 tip "edit hide"
199 rebasing 2:b91df4f39e75 tip "edit hide"
200 temporarily included 2 file(s) in the sparse checkout for merging
200 temporarily included 2 file(s) in the sparse checkout for merging
201 merging hide
201 merging hide
202 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
202 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
203 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
203 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
204 [240]
204 [240]
205
205
206 $ hg debugsparse
206 $ hg debugsparse
207 [exclude]
207 [exclude]
208 hide*
208 hide*
209
209
210 Temporarily Included Files (for merge/rebase):
210 Temporarily Included Files (for merge/rebase):
211 hide
211 hide
212
212
213 $ cat hide
213 $ cat hide
214 <<<<<<< dest: 39278f7c08a9 - test: two
214 <<<<<<< dest: 39278f7c08a9 - test: two
215 y
215 y
216 =======
216 =======
217 z
217 z
218 >>>>>>> source: b91df4f39e75 - test: edit hide
218 >>>>>>> source: b91df4f39e75 - test: edit hide
219
219
220 Verify aborting a rebase cleans up temporary files
220 Verify aborting a rebase cleans up temporary files
221
221
222 $ hg rebase --abort --config extensions.rebase=
222 $ hg rebase --abort --config extensions.rebase=
223 cleaned up 1 temporarily added file(s) from the sparse checkout
223 cleaned up 1 temporarily added file(s) from the sparse checkout
224 rebase aborted
224 rebase aborted
225 $ rm hide.orig
225 $ rm hide.orig
226
226
227 $ ls -A
227 $ ls -A
228 .hg
228 .hg
229 show
229 show
230 show2
230 show2
231
231
232 Verify merge fails if merging excluded files
232 Verify merge fails if merging excluded files
233
233
234 $ hg up -q 1
234 $ hg up -q 1
235 $ hg merge -r 2
235 $ hg merge -r 2
236 temporarily included 2 file(s) in the sparse checkout for merging
236 temporarily included 2 file(s) in the sparse checkout for merging
237 merging hide
237 merging hide
238 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
238 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
239 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
239 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
240 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
240 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
241 [1]
241 [1]
242 $ hg debugsparse
242 $ hg debugsparse
243 [exclude]
243 [exclude]
244 hide*
244 hide*
245
245
246 Temporarily Included Files (for merge/rebase):
246 Temporarily Included Files (for merge/rebase):
247 hide
247 hide
248
248
249 $ hg up -C .
249 $ hg up -C .
250 cleaned up 1 temporarily added file(s) from the sparse checkout
250 cleaned up 1 temporarily added file(s) from the sparse checkout
251 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
251 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 $ hg debugsparse
252 $ hg debugsparse
253 [exclude]
253 [exclude]
254 hide*
254 hide*
255
255
256
256
257 Verify strip -k resets dirstate correctly
257 Verify strip -k resets dirstate correctly
258
258
259 $ hg status
259 $ hg status
260 $ hg debugsparse
260 $ hg debugsparse
261 [exclude]
261 [exclude]
262 hide*
262 hide*
263
263
264 $ hg log -r . -T '{rev}\n' --stat
264 $ hg log -r . -T '{rev}\n' --stat
265 1
265 1
266 hide | 2 +-
266 hide | 2 +-
267 hide2 | 1 +
267 hide2 | 1 +
268 show | 2 +-
268 show | 2 +-
269 show2 | 1 +
269 show2 | 1 +
270 4 files changed, 4 insertions(+), 2 deletions(-)
270 4 files changed, 4 insertions(+), 2 deletions(-)
271
271
272 $ hg strip -r . -k
272 $ hg strip -r . -k
273 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/39278f7c08a9-ce59e002-backup.hg
273 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/39278f7c08a9-ce59e002-backup.hg
274 $ hg status
274 $ hg status
275 M show
275 M show
276 ? show2
276 ? show2
277
277
278 Verify rebase succeeds if all changed files are in sparse checkout
278 Verify rebase succeeds if all changed files are in sparse checkout
279
279
280 $ hg commit -Aqm "add show2"
280 $ hg commit -Aqm "add show2"
281 $ hg rebase -d 1 --config extensions.rebase=
281 $ hg rebase -d 1 --config extensions.rebase=
282 rebasing 2:bdde55290160 tip "add show2"
282 rebasing 2:bdde55290160 tip "add show2"
283 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/bdde55290160-216ed9c6-rebase.hg
283 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/bdde55290160-216ed9c6-rebase.hg
284
284
285 Verify log --sparse only shows commits that affect the sparse checkout
285 Verify log --sparse only shows commits that affect the sparse checkout
286
286
287 $ hg log -T '{rev} '
287 $ hg log -T '{rev} '
288 2 1 0 (no-eol)
288 2 1 0 (no-eol)
289 $ hg log --sparse -T '{rev} '
289 $ hg log --sparse -T '{rev} '
290 2 0 (no-eol)
290 2 0 (no-eol)
291
291
292 Test status on a file in a subdir
292 Test status on a file in a subdir
293
293
294 $ mkdir -p dir1/dir2
294 $ mkdir -p dir1/dir2
295 $ touch dir1/dir2/file
295 $ touch dir1/dir2/file
296 $ hg debugsparse -I dir1/dir2
296 $ hg debugsparse -I dir1/dir2
297 $ hg status
297 $ hg status
298 ? dir1/dir2/file
298 ? dir1/dir2/file
299
299
300 Mix files and subdirectories, both "glob:" and unprefixed
300 Mix files and subdirectories, both "glob:" and unprefixed
301
301
302 $ hg debugsparse --reset
302 $ hg debugsparse --reset
303 $ touch dir1/notshown
303 $ touch dir1/notshown
304 $ hg commit -A dir1/notshown -m "notshown"
304 $ hg commit -A dir1/notshown -m "notshown"
305 $ hg debugsparse --include 'dir1/dir2'
305 $ hg debugsparse --include 'dir1/dir2'
306 $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
306 $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
307 ./
307 ./
308 ./dir1/
308 ./dir1/
309 ./dir1/dir2/
309 ./dir1/dir2/
310 ./dir1/dir2/file
310 ./dir1/dir2/file
311 ./hide.orig
311 ./hide.orig
312 $ hg debugsparse --delete 'dir1/dir2'
312 $ hg debugsparse --delete 'dir1/dir2'
313 $ hg debugsparse --include 'glob:dir1/dir2'
313 $ hg debugsparse --include 'glob:dir1/dir2'
314 $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
314 $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
315 ./
315 ./
316 ./dir1/
316 ./dir1/
317 ./dir1/dir2/
317 ./dir1/dir2/
318 ./dir1/dir2/file
318 ./dir1/dir2/file
319 ./hide.orig
319 ./hide.orig
320
320
321 Test that add -s adds dirs to sparse profile
321 Test that add -s adds dirs to sparse profile
322
322
323 $ hg debugsparse --reset
323 $ hg debugsparse --reset
324 $ hg debugsparse --include empty
324 $ hg debugsparse --include empty
325 $ hg debugsparse
325 $ hg debugsparse
326 [include]
326 [include]
327 empty
327 empty
328
328
329
329
330 $ mkdir add
330 $ mkdir add
331 $ touch add/foo
331 $ touch add/foo
332 $ touch add/bar
332 $ touch add/bar
333 $ hg add add/foo
333 $ hg add add/foo
334 abort: cannot add 'add/foo' - it is outside the sparse checkout
334 abort: cannot add 'add/foo' - it is outside the sparse checkout
335 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
335 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
336 [255]
336 [255]
337 $ hg add -s add/foo
337 $ hg add -s add/foo
338 $ hg st
338 $ hg st
339 A add/foo
339 A add/foo
340 ? add/bar
340 ? add/bar
341 $ hg debugsparse
341 $ hg debugsparse
342 [include]
342 [include]
343 add
343 add
344 empty
344 empty
345
345
346 $ hg add -s add/*
346 $ hg add -s add/*
347 add/foo already tracked!
347 add/foo already tracked!
348 $ hg st
348 $ hg st
349 A add/bar
349 A add/bar
350 A add/foo
350 A add/foo
351 $ hg debugsparse
351 $ hg debugsparse
352 [include]
352 [include]
353 add
353 add
354 empty
354 empty
355
355
356
356
357 $ cd ..
357 $ cd ..
358
358
359 Test non-sparse repos work while sparse is loaded
359 Test non-sparse repos work while sparse is loaded
360 $ hg init sparserepo
360 $ hg init sparserepo
361 $ hg init nonsparserepo
361 $ hg init nonsparserepo
362 $ cd sparserepo
362 $ cd sparserepo
363 $ cat > .hg/hgrc <<EOF
363 $ cat > .hg/hgrc <<EOF
364 > [extensions]
364 > [extensions]
365 > sparse=
365 > sparse=
366 > EOF
366 > EOF
367 $ cd ../nonsparserepo
367 $ cd ../nonsparserepo
368 $ echo x > x && hg add x && hg commit -qAm x
368 $ echo x > x && hg add x && hg commit -qAm x
369 $ cd ../sparserepo
369 $ cd ../sparserepo
370 $ hg clone ../nonsparserepo ../nonsparserepo2
370 $ hg clone ../nonsparserepo ../nonsparserepo2
371 updating to branch default
371 updating to branch default
372 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
372 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
373
373
374 Test debugrebuilddirstate
374 Test debugrebuilddirstate
375 $ cd ../sparserepo
375 $ cd ../sparserepo
376 $ touch included
376 $ touch included
377 $ touch excluded
377 $ touch excluded
378 $ hg add included excluded
378 $ hg add included excluded
379 $ hg commit -m 'a commit' -q
379 $ hg commit -m 'a commit' -q
380 $ cp .hg/dirstate ../dirstateboth
380 $ cp .hg/dirstate ../dirstateboth
381 $ hg debugsparse -X excluded
381 $ hg debugsparse -X excluded
382 $ cp ../dirstateboth .hg/dirstate
382 $ cp ../dirstateboth .hg/dirstate
383 $ hg debugrebuilddirstate
383 $ hg debugrebuilddirstate
384 $ hg debugdirstate
384 $ hg debugdirstate
385 n 0 -1 unset included
385 n 0 -1 unset included
386
386
387 Test debugdirstate --minimal where file is in the parent manifest but not the
387 Test debugdirstate --minimal where file is in the parent manifest but not the
388 dirstate
388 dirstate
389 $ hg debugsparse -X included
389 $ hg debugsparse -X included
390 $ hg debugdirstate
390 $ hg debugdirstate
391 $ cp .hg/dirstate ../dirstateallexcluded
391 $ cp .hg/dirstate ../dirstateallexcluded
392 $ hg debugsparse --reset
392 $ hg debugsparse --reset
393 $ hg debugsparse -X excluded
393 $ hg debugsparse -X excluded
394 $ cp ../dirstateallexcluded .hg/dirstate
394 $ cp ../dirstateallexcluded .hg/dirstate
395 $ touch includedadded
395 $ touch includedadded
396 $ hg add includedadded
396 $ hg add includedadded
397 $ hg debugdirstate --no-dates
397 $ hg debugdirstate --no-dates
398 a 0 -1 unset includedadded
398 a 0 -1 unset includedadded
399 $ hg debugrebuilddirstate --minimal
399 $ hg debugrebuilddirstate --minimal
400 $ hg debugdirstate --no-dates
400 $ hg debugdirstate --no-dates
401 n 0 -1 unset included
401 n 0 -1 unset included
402 a 0 -1 * includedadded (glob)
402 a 0 -1 * includedadded (glob)
403
403
404 Test debugdirstate --minimal where a file is not in parent manifest
404 Test debugdirstate --minimal where a file is not in parent manifest
405 but in the dirstate. This should take into account excluded files in the
405 but in the dirstate. This should take into account excluded files in the
406 manifest
406 manifest
407 $ cp ../dirstateboth .hg/dirstate
407 $ cp ../dirstateboth .hg/dirstate
408 $ touch includedadded
408 $ touch includedadded
409 $ hg add includedadded
409 $ hg add includedadded
410 $ touch excludednomanifest
410 $ touch excludednomanifest
411 $ hg add excludednomanifest
411 $ hg add excludednomanifest
412 $ cp .hg/dirstate ../moreexcluded
412 $ cp .hg/dirstate ../moreexcluded
413 $ hg forget excludednomanifest
413 $ hg forget excludednomanifest
414 $ rm excludednomanifest
414 $ rm excludednomanifest
415 $ hg debugsparse -X excludednomanifest
415 $ hg debugsparse -X excludednomanifest
416 $ cp ../moreexcluded .hg/dirstate
416 $ cp ../moreexcluded .hg/dirstate
417 $ hg manifest
417 $ hg manifest
418 excluded
418 excluded
419 included
419 included
420 We have files in the dirstate that are included and excluded. Some are in the
420 We have files in the dirstate that are included and excluded. Some are in the
421 manifest and some are not.
421 manifest and some are not.
422 $ hg debugdirstate --no-dates
422 $ hg debugdirstate --no-dates
423 n 644 0 * excluded (glob)
423 n * excluded (glob)
424 a 0 -1 * excludednomanifest (glob)
424 a * excludednomanifest (glob)
425 n 644 0 * included (glob)
425 n * included (glob)
426 a 0 -1 * includedadded (glob)
426 a * includedadded (glob)
427 $ hg debugrebuilddirstate --minimal
427 $ hg debugrebuilddirstate --minimal
428 $ hg debugdirstate --no-dates
428 $ hg debugdirstate --no-dates
429 n 644 0 * included (glob)
429 n * included (glob)
430 a 0 -1 * includedadded (glob)
430 a * includedadded (glob)
431
431
General Comments 0
You need to be logged in to leave comments. Login now