##// END OF EJS Templates
sparse: rework debugsparse's interface...
Valentin Gatien-Baron -
r49588:a6efb918 default
parent child Browse files
Show More
@@ -1,437 +1,455 b''
1 # sparse.py - allow sparse checkouts of the working directory
1 # sparse.py - allow sparse checkouts of the working directory
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 """allow sparse checkouts of the working directory (EXPERIMENTAL)
8 """allow sparse checkouts of the working directory (EXPERIMENTAL)
9
9
10 (This extension is not yet protected by backwards compatibility
10 (This extension is not yet protected by backwards compatibility
11 guarantees. Any aspect may break in future releases until this
11 guarantees. Any aspect may break in future releases until this
12 notice is removed.)
12 notice is removed.)
13
13
14 This extension allows the working directory to only consist of a
14 This extension allows the working directory to only consist of a
15 subset of files for the revision. This allows specific files or
15 subset of files for the revision. This allows specific files or
16 directories to be explicitly included or excluded. Many repository
16 directories to be explicitly included or excluded. Many repository
17 operations have performance proportional to the number of files in
17 operations have performance proportional to the number of files in
18 the working directory. So only realizing a subset of files in the
18 the working directory. So only realizing a subset of files in the
19 working directory can improve performance.
19 working directory can improve performance.
20
20
21 Sparse Config Files
21 Sparse Config Files
22 -------------------
22 -------------------
23
23
24 The set of files that are part of a sparse checkout are defined by
24 The set of files that are part of a sparse checkout are defined by
25 a sparse config file. The file defines 3 things: includes (files to
25 a sparse config file. The file defines 3 things: includes (files to
26 include in the sparse checkout), excludes (files to exclude from the
26 include in the sparse checkout), excludes (files to exclude from the
27 sparse checkout), and profiles (links to other config files).
27 sparse checkout), and profiles (links to other config files).
28
28
29 The file format is newline delimited. Empty lines and lines beginning
29 The file format is newline delimited. Empty lines and lines beginning
30 with ``#`` are ignored.
30 with ``#`` are ignored.
31
31
32 Lines beginning with ``%include `` denote another sparse config file
32 Lines beginning with ``%include `` denote another sparse config file
33 to include. e.g. ``%include tests.sparse``. The filename is relative
33 to include. e.g. ``%include tests.sparse``. The filename is relative
34 to the repository root.
34 to the repository root.
35
35
36 The special lines ``[include]`` and ``[exclude]`` denote the section
36 The special lines ``[include]`` and ``[exclude]`` denote the section
37 for includes and excludes that follow, respectively. It is illegal to
37 for includes and excludes that follow, respectively. It is illegal to
38 have ``[include]`` after ``[exclude]``.
38 have ``[include]`` after ``[exclude]``.
39
39
40 Non-special lines resemble file patterns to be added to either includes
40 Non-special lines resemble file patterns to be added to either includes
41 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
41 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
42 Patterns are interpreted as ``glob:`` by default and match against the
42 Patterns are interpreted as ``glob:`` by default and match against the
43 root of the repository.
43 root of the repository.
44
44
45 Exclusion patterns take precedence over inclusion patterns. So even
45 Exclusion patterns take precedence over inclusion patterns. So even
46 if a file is explicitly included, an ``[exclude]`` entry can remove it.
46 if a file is explicitly included, an ``[exclude]`` entry can remove it.
47
47
48 For example, say you have a repository with 3 directories, ``frontend/``,
48 For example, say you have a repository with 3 directories, ``frontend/``,
49 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
49 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
50 to different projects and it is uncommon for someone working on one
50 to different projects and it is uncommon for someone working on one
51 to need the files for the other. But ``tools/`` contains files shared
51 to need the files for the other. But ``tools/`` contains files shared
52 between both projects. Your sparse config files may resemble::
52 between both projects. Your sparse config files may resemble::
53
53
54 # frontend.sparse
54 # frontend.sparse
55 frontend/**
55 frontend/**
56 tools/**
56 tools/**
57
57
58 # backend.sparse
58 # backend.sparse
59 backend/**
59 backend/**
60 tools/**
60 tools/**
61
61
62 Say the backend grows in size. Or there's a directory with thousands
62 Say the backend grows in size. Or there's a directory with thousands
63 of files you wish to exclude. You can modify the profile to exclude
63 of files you wish to exclude. You can modify the profile to exclude
64 certain files::
64 certain files::
65
65
66 [include]
66 [include]
67 backend/**
67 backend/**
68 tools/**
68 tools/**
69
69
70 [exclude]
70 [exclude]
71 tools/tests/**
71 tools/tests/**
72 """
72 """
73
73
74 from __future__ import absolute_import
74 from __future__ import absolute_import
75
75
76 from mercurial.i18n import _
76 from mercurial.i18n import _
77 from mercurial.pycompat import setattr
77 from mercurial.pycompat import setattr
78 from mercurial import (
78 from mercurial import (
79 cmdutil,
79 commands,
80 commands,
80 dirstate,
81 dirstate,
81 error,
82 error,
82 extensions,
83 extensions,
83 logcmdutil,
84 logcmdutil,
84 match as matchmod,
85 match as matchmod,
85 merge as mergemod,
86 merge as mergemod,
86 pycompat,
87 pycompat,
87 registrar,
88 registrar,
88 sparse,
89 sparse,
89 util,
90 util,
90 )
91 )
91
92
92 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
93 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
93 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
94 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
94 # be specifying the version(s) of Mercurial they are tested with, or
95 # be specifying the version(s) of Mercurial they are tested with, or
95 # leave the attribute unspecified.
96 # leave the attribute unspecified.
96 testedwith = b'ships-with-hg-core'
97 testedwith = b'ships-with-hg-core'
97
98
98 cmdtable = {}
99 cmdtable = {}
99 command = registrar.command(cmdtable)
100 command = registrar.command(cmdtable)
100
101
101
102
102 def extsetup(ui):
103 def extsetup(ui):
103 sparse.enabled = True
104 sparse.enabled = True
104
105
105 _setupclone(ui)
106 _setupclone(ui)
106 _setuplog(ui)
107 _setuplog(ui)
107 _setupadd(ui)
108 _setupadd(ui)
108 _setupdirstate(ui)
109 _setupdirstate(ui)
109
110
110
111
111 def replacefilecache(cls, propname, replacement):
112 def replacefilecache(cls, propname, replacement):
112 """Replace a filecache property with a new class. This allows changing the
113 """Replace a filecache property with a new class. This allows changing the
113 cache invalidation condition."""
114 cache invalidation condition."""
114 origcls = cls
115 origcls = cls
115 assert callable(replacement)
116 assert callable(replacement)
116 while cls is not object:
117 while cls is not object:
117 if propname in cls.__dict__:
118 if propname in cls.__dict__:
118 orig = cls.__dict__[propname]
119 orig = cls.__dict__[propname]
119 setattr(cls, propname, replacement(orig))
120 setattr(cls, propname, replacement(orig))
120 break
121 break
121 cls = cls.__bases__[0]
122 cls = cls.__bases__[0]
122
123
123 if cls is object:
124 if cls is object:
124 raise AttributeError(
125 raise AttributeError(
125 _(b"type '%s' has no property '%s'") % (origcls, propname)
126 _(b"type '%s' has no property '%s'") % (origcls, propname)
126 )
127 )
127
128
128
129
129 def _setuplog(ui):
130 def _setuplog(ui):
130 entry = commands.table[b'log|history']
131 entry = commands.table[b'log|history']
131 entry[1].append(
132 entry[1].append(
132 (
133 (
133 b'',
134 b'',
134 b'sparse',
135 b'sparse',
135 None,
136 None,
136 b"limit to changesets affecting the sparse checkout",
137 b"limit to changesets affecting the sparse checkout",
137 )
138 )
138 )
139 )
139
140
140 def _initialrevs(orig, repo, wopts):
141 def _initialrevs(orig, repo, wopts):
141 revs = orig(repo, wopts)
142 revs = orig(repo, wopts)
142 if wopts.opts.get(b'sparse'):
143 if wopts.opts.get(b'sparse'):
143 sparsematch = sparse.matcher(repo)
144 sparsematch = sparse.matcher(repo)
144
145
145 def ctxmatch(rev):
146 def ctxmatch(rev):
146 ctx = repo[rev]
147 ctx = repo[rev]
147 return any(f for f in ctx.files() if sparsematch(f))
148 return any(f for f in ctx.files() if sparsematch(f))
148
149
149 revs = revs.filter(ctxmatch)
150 revs = revs.filter(ctxmatch)
150 return revs
151 return revs
151
152
152 extensions.wrapfunction(logcmdutil, b'_initialrevs', _initialrevs)
153 extensions.wrapfunction(logcmdutil, b'_initialrevs', _initialrevs)
153
154
154
155
155 def _clonesparsecmd(orig, ui, repo, *args, **opts):
156 def _clonesparsecmd(orig, ui, repo, *args, **opts):
156 include_pat = opts.get('include')
157 include = opts.get('include')
157 exclude_pat = opts.get('exclude')
158 exclude = opts.get('exclude')
158 enableprofile_pat = opts.get('enable_profile')
159 enableprofile = opts.get('enable_profile')
159 narrow_pat = opts.get('narrow')
160 narrow_pat = opts.get('narrow')
160 include = exclude = enableprofile = False
161
161 if include_pat:
162 pat = include_pat
163 include = True
164 if exclude_pat:
165 pat = exclude_pat
166 exclude = True
167 if enableprofile_pat:
168 pat = enableprofile_pat
169 enableprofile = True
170 if sum([include, exclude, enableprofile]) > 1:
171 raise error.Abort(_(b"too many flags specified."))
172 # if --narrow is passed, it means they are includes and excludes for narrow
162 # if --narrow is passed, it means they are includes and excludes for narrow
173 # clone
163 # clone
174 if not narrow_pat and (include or exclude or enableprofile):
164 if not narrow_pat and (include or exclude or enableprofile):
175
165
176 def clonesparse(orig, ctx, *args, **kwargs):
166 def clonesparse(orig, ctx, *args, **kwargs):
177 sparse.updateconfig(
167 sparse.updateconfig(
178 ctx.repo().unfiltered(),
168 ctx.repo().unfiltered(),
179 pat,
180 {},
169 {},
181 include=include,
170 include=include,
182 exclude=exclude,
171 exclude=exclude,
183 enableprofile=enableprofile,
172 enableprofile=enableprofile,
184 usereporootpaths=True,
173 usereporootpaths=True,
185 )
174 )
186 return orig(ctx, *args, **kwargs)
175 return orig(ctx, *args, **kwargs)
187
176
188 extensions.wrapfunction(mergemod, b'update', clonesparse)
177 extensions.wrapfunction(mergemod, b'update', clonesparse)
189 return orig(ui, repo, *args, **opts)
178 return orig(ui, repo, *args, **opts)
190
179
191
180
192 def _setupclone(ui):
181 def _setupclone(ui):
193 entry = commands.table[b'clone']
182 entry = commands.table[b'clone']
194 entry[1].append((b'', b'enable-profile', [], b'enable a sparse profile'))
183 entry[1].append((b'', b'enable-profile', [], b'enable a sparse profile'))
195 entry[1].append((b'', b'include', [], b'include sparse pattern'))
184 entry[1].append((b'', b'include', [], b'include sparse pattern'))
196 entry[1].append((b'', b'exclude', [], b'exclude sparse pattern'))
185 entry[1].append((b'', b'exclude', [], b'exclude sparse pattern'))
197 extensions.wrapcommand(commands.table, b'clone', _clonesparsecmd)
186 extensions.wrapcommand(commands.table, b'clone', _clonesparsecmd)
198
187
199
188
200 def _setupadd(ui):
189 def _setupadd(ui):
201 entry = commands.table[b'add']
190 entry = commands.table[b'add']
202 entry[1].append(
191 entry[1].append(
203 (
192 (
204 b's',
193 b's',
205 b'sparse',
194 b'sparse',
206 None,
195 None,
207 b'also include directories of added files in sparse config',
196 b'also include directories of added files in sparse config',
208 )
197 )
209 )
198 )
210
199
211 def _add(orig, ui, repo, *pats, **opts):
200 def _add(orig, ui, repo, *pats, **opts):
212 if opts.get('sparse'):
201 if opts.get('sparse'):
213 dirs = set()
202 dirs = set()
214 for pat in pats:
203 for pat in pats:
215 dirname, basename = util.split(pat)
204 dirname, basename = util.split(pat)
216 dirs.add(dirname)
205 dirs.add(dirname)
217 sparse.updateconfig(repo, list(dirs), opts, include=True)
206 sparse.updateconfig(repo, opts, include=list(dirs))
218 return orig(ui, repo, *pats, **opts)
207 return orig(ui, repo, *pats, **opts)
219
208
220 extensions.wrapcommand(commands.table, b'add', _add)
209 extensions.wrapcommand(commands.table, b'add', _add)
221
210
222
211
223 def _setupdirstate(ui):
212 def _setupdirstate(ui):
224 """Modify the dirstate to prevent stat'ing excluded files,
213 """Modify the dirstate to prevent stat'ing excluded files,
225 and to prevent modifications to files outside the checkout.
214 and to prevent modifications to files outside the checkout.
226 """
215 """
227
216
228 def walk(orig, self, match, subrepos, unknown, ignored, full=True):
217 def walk(orig, self, match, subrepos, unknown, ignored, full=True):
229 # hack to not exclude explicitly-specified paths so that they can
218 # hack to not exclude explicitly-specified paths so that they can
230 # be warned later on e.g. dirstate.add()
219 # be warned later on e.g. dirstate.add()
231 em = matchmod.exact(match.files())
220 em = matchmod.exact(match.files())
232 sm = matchmod.unionmatcher([self._sparsematcher, em])
221 sm = matchmod.unionmatcher([self._sparsematcher, em])
233 match = matchmod.intersectmatchers(match, sm)
222 match = matchmod.intersectmatchers(match, sm)
234 return orig(self, match, subrepos, unknown, ignored, full)
223 return orig(self, match, subrepos, unknown, ignored, full)
235
224
236 extensions.wrapfunction(dirstate.dirstate, b'walk', walk)
225 extensions.wrapfunction(dirstate.dirstate, b'walk', walk)
237
226
238 # dirstate.rebuild should not add non-matching files
227 # dirstate.rebuild should not add non-matching files
239 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
228 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
240 matcher = self._sparsematcher
229 matcher = self._sparsematcher
241 if not matcher.always():
230 if not matcher.always():
242 allfiles = [f for f in allfiles if matcher(f)]
231 allfiles = [f for f in allfiles if matcher(f)]
243 if changedfiles:
232 if changedfiles:
244 changedfiles = [f for f in changedfiles if matcher(f)]
233 changedfiles = [f for f in changedfiles if matcher(f)]
245
234
246 if changedfiles is not None:
235 if changedfiles is not None:
247 # In _rebuild, these files will be deleted from the dirstate
236 # In _rebuild, these files will be deleted from the dirstate
248 # when they are not found to be in allfiles
237 # when they are not found to be in allfiles
249 dirstatefilestoremove = {f for f in self if not matcher(f)}
238 dirstatefilestoremove = {f for f in self if not matcher(f)}
250 changedfiles = dirstatefilestoremove.union(changedfiles)
239 changedfiles = dirstatefilestoremove.union(changedfiles)
251
240
252 return orig(self, parent, allfiles, changedfiles)
241 return orig(self, parent, allfiles, changedfiles)
253
242
254 extensions.wrapfunction(dirstate.dirstate, b'rebuild', _rebuild)
243 extensions.wrapfunction(dirstate.dirstate, b'rebuild', _rebuild)
255
244
256 # Prevent adding files that are outside the sparse checkout
245 # Prevent adding files that are outside the sparse checkout
257 editfuncs = [
246 editfuncs = [
258 b'set_tracked',
247 b'set_tracked',
259 b'set_untracked',
248 b'set_untracked',
260 b'copy',
249 b'copy',
261 ]
250 ]
262 hint = _(
251 hint = _(
263 b'include file with `hg debugsparse --include <pattern>` or use '
252 b'include file with `hg debugsparse --include <pattern>` or use '
264 + b'`hg add -s <file>` to include file directory while adding'
253 + b'`hg add -s <file>` to include file directory while adding'
265 )
254 )
266 for func in editfuncs:
255 for func in editfuncs:
267
256
268 def _wrapper(orig, self, *args, **kwargs):
257 def _wrapper(orig, self, *args, **kwargs):
269 sparsematch = self._sparsematcher
258 sparsematch = self._sparsematcher
270 if not sparsematch.always():
259 if not sparsematch.always():
271 for f in args:
260 for f in args:
272 if f is not None and not sparsematch(f) and f not in self:
261 if f is not None and not sparsematch(f) and f not in self:
273 raise error.Abort(
262 raise error.Abort(
274 _(
263 _(
275 b"cannot add '%s' - it is outside "
264 b"cannot add '%s' - it is outside "
276 b"the sparse checkout"
265 b"the sparse checkout"
277 )
266 )
278 % f,
267 % f,
279 hint=hint,
268 hint=hint,
280 )
269 )
281 return orig(self, *args, **kwargs)
270 return orig(self, *args, **kwargs)
282
271
283 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
272 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
284
273
285
274
286 @command(
275 @command(
287 b'debugsparse',
276 b'debugsparse',
288 [
277 [
289 (b'I', b'include', False, _(b'include files in the sparse checkout')),
278 (
290 (b'X', b'exclude', False, _(b'exclude files in the sparse checkout')),
279 b'I',
291 (b'd', b'delete', False, _(b'delete an include/exclude rule')),
280 b'include',
281 [],
282 _(b'include files in the sparse checkout'),
283 _(b'PATTERN'),
284 ),
285 (
286 b'X',
287 b'exclude',
288 [],
289 _(b'exclude files in the sparse checkout'),
290 _(b'PATTERN'),
291 ),
292 (
293 b'd',
294 b'delete',
295 [],
296 _(b'delete an include/exclude rule'),
297 _(b'PATTERN'),
298 ),
292 (
299 (
293 b'f',
300 b'f',
294 b'force',
301 b'force',
295 False,
302 False,
296 _(b'allow changing rules even with pending changes'),
303 _(b'allow changing rules even with pending changes'),
297 ),
304 ),
298 (b'', b'enable-profile', False, _(b'enables the specified profile')),
305 (
299 (b'', b'disable-profile', False, _(b'disables the specified profile')),
306 b'',
300 (b'', b'import-rules', False, _(b'imports rules from a file')),
307 b'enable-profile',
308 [],
309 _(b'enables the specified profile'),
310 _(b'PATTERN'),
311 ),
312 (
313 b'',
314 b'disable-profile',
315 [],
316 _(b'disables the specified profile'),
317 _(b'PATTERN'),
318 ),
319 (
320 b'',
321 b'import-rules',
322 [],
323 _(b'imports rules from a file'),
324 _(b'PATTERN'),
325 ),
301 (b'', b'clear-rules', False, _(b'clears local include/exclude rules')),
326 (b'', b'clear-rules', False, _(b'clears local include/exclude rules')),
302 (
327 (
303 b'',
328 b'',
304 b'refresh',
329 b'refresh',
305 False,
330 False,
306 _(b'updates the working after sparseness changes'),
331 _(b'updates the working after sparseness changes'),
307 ),
332 ),
308 (b'', b'reset', False, _(b'makes the repo full again')),
333 (b'', b'reset', False, _(b'makes the repo full again')),
309 ]
334 ]
310 + commands.templateopts,
335 + commands.templateopts,
311 _(b'[--OPTION] PATTERN...'),
336 _(b'[--OPTION]'),
312 helpbasic=True,
337 helpbasic=True,
313 )
338 )
314 def debugsparse(ui, repo, *pats, **opts):
339 def debugsparse(ui, repo, **opts):
315 """make the current checkout sparse, or edit the existing checkout
340 """make the current checkout sparse, or edit the existing checkout
316
341
317 The sparse command is used to make the current checkout sparse.
342 The sparse command is used to make the current checkout sparse.
318 This means files that don't meet the sparse condition will not be
343 This means files that don't meet the sparse condition will not be
319 written to disk, or show up in any working copy operations. It does
344 written to disk, or show up in any working copy operations. It does
320 not affect files in history in any way.
345 not affect files in history in any way.
321
346
322 Passing no arguments prints the currently applied sparse rules.
347 Passing no arguments prints the currently applied sparse rules.
323
348
324 --include and --exclude are used to add and remove files from the sparse
349 --include and --exclude are used to add and remove files from the sparse
325 checkout. The effects of adding an include or exclude rule are applied
350 checkout. The effects of adding an include or exclude rule are applied
326 immediately. If applying the new rule would cause a file with pending
351 immediately. If applying the new rule would cause a file with pending
327 changes to be added or removed, the command will fail. Pass --force to
352 changes to be added or removed, the command will fail. Pass --force to
328 force a rule change even with pending changes (the changes on disk will
353 force a rule change even with pending changes (the changes on disk will
329 be preserved).
354 be preserved).
330
355
331 --delete removes an existing include/exclude rule. The effects are
356 --delete removes an existing include/exclude rule. The effects are
332 immediate.
357 immediate.
333
358
334 --refresh refreshes the files on disk based on the sparse rules. This is
359 --refresh refreshes the files on disk based on the sparse rules. This is
335 only necessary if .hg/sparse was changed by hand.
360 only necessary if .hg/sparse was changed by hand.
336
361
337 --enable-profile and --disable-profile accept a path to a .hgsparse file.
362 --enable-profile and --disable-profile accept a path to a .hgsparse file.
338 This allows defining sparse checkouts and tracking them inside the
363 This allows defining sparse checkouts and tracking them inside the
339 repository. This is useful for defining commonly used sparse checkouts for
364 repository. This is useful for defining commonly used sparse checkouts for
340 many people to use. As the profile definition changes over time, the sparse
365 many people to use. As the profile definition changes over time, the sparse
341 checkout will automatically be updated appropriately, depending on which
366 checkout will automatically be updated appropriately, depending on which
342 changeset is checked out. Changes to .hgsparse are not applied until they
367 changeset is checked out. Changes to .hgsparse are not applied until they
343 have been committed.
368 have been committed.
344
369
345 --import-rules accepts a path to a file containing rules in the .hgsparse
370 --import-rules accepts a path to a file containing rules in the .hgsparse
346 format, allowing you to add --include, --exclude and --enable-profile rules
371 format, allowing you to add --include, --exclude and --enable-profile rules
347 in bulk. Like the --include, --exclude and --enable-profile switches, the
372 in bulk. Like the --include, --exclude and --enable-profile switches, the
348 changes are applied immediately.
373 changes are applied immediately.
349
374
350 --clear-rules removes all local include and exclude rules, while leaving
375 --clear-rules removes all local include and exclude rules, while leaving
351 any enabled profiles in place.
376 any enabled profiles in place.
352
377
353 Returns 0 if editing the sparse checkout succeeds.
378 Returns 0 if editing the sparse checkout succeeds.
354 """
379 """
355 opts = pycompat.byteskwargs(opts)
380 opts = pycompat.byteskwargs(opts)
356 include = opts.get(b'include')
381 include = opts.get(b'include')
357 exclude = opts.get(b'exclude')
382 exclude = opts.get(b'exclude')
358 force = opts.get(b'force')
383 force = opts.get(b'force')
359 enableprofile = opts.get(b'enable_profile')
384 enableprofile = opts.get(b'enable_profile')
360 disableprofile = opts.get(b'disable_profile')
385 disableprofile = opts.get(b'disable_profile')
361 importrules = opts.get(b'import_rules')
386 importrules = opts.get(b'import_rules')
362 clearrules = opts.get(b'clear_rules')
387 clearrules = opts.get(b'clear_rules')
363 delete = opts.get(b'delete')
388 delete = opts.get(b'delete')
364 refresh = opts.get(b'refresh')
389 refresh = opts.get(b'refresh')
365 reset = opts.get(b'reset')
390 reset = opts.get(b'reset')
366 count = sum(
391 action = cmdutil.check_at_most_one_arg(
367 [
392 opts, b'import_rules', b'clear_rules', b'refresh'
368 include,
369 exclude,
370 enableprofile,
371 disableprofile,
372 delete,
373 importrules,
374 refresh,
375 clearrules,
376 reset,
377 ]
378 )
393 )
394 updateconfig = bool(
395 include or exclude or delete or reset or enableprofile or disableprofile
396 )
397 count = sum([updateconfig, bool(action)])
379 if count > 1:
398 if count > 1:
380 raise error.Abort(_(b"too many flags specified"))
399 raise error.Abort(_(b"too many flags specified"))
381
400
382 if count == 0:
401 if count == 0:
383 if repo.vfs.exists(b'sparse'):
402 if repo.vfs.exists(b'sparse'):
384 ui.status(repo.vfs.read(b"sparse") + b"\n")
403 ui.status(repo.vfs.read(b"sparse") + b"\n")
385 temporaryincludes = sparse.readtemporaryincludes(repo)
404 temporaryincludes = sparse.readtemporaryincludes(repo)
386 if temporaryincludes:
405 if temporaryincludes:
387 ui.status(
406 ui.status(
388 _(b"Temporarily Included Files (for merge/rebase):\n")
407 _(b"Temporarily Included Files (for merge/rebase):\n")
389 )
408 )
390 ui.status((b"\n".join(temporaryincludes) + b"\n"))
409 ui.status((b"\n".join(temporaryincludes) + b"\n"))
391 return
410 return
392 else:
411 else:
393 raise error.Abort(
412 raise error.Abort(
394 _(
413 _(
395 b'the debugsparse command is only supported on'
414 b'the debugsparse command is only supported on'
396 b' sparse repositories'
415 b' sparse repositories'
397 )
416 )
398 )
417 )
399
418
400 if include or exclude or delete or reset or enableprofile or disableprofile:
419 if updateconfig:
401 sparse.updateconfig(
420 sparse.updateconfig(
402 repo,
421 repo,
403 pats,
404 opts,
422 opts,
405 include=include,
423 include=include,
406 exclude=exclude,
424 exclude=exclude,
407 reset=reset,
425 reset=reset,
408 delete=delete,
426 delete=delete,
409 enableprofile=enableprofile,
427 enableprofile=enableprofile,
410 disableprofile=disableprofile,
428 disableprofile=disableprofile,
411 force=force,
429 force=force,
412 )
430 )
413
431
414 if importrules:
432 if importrules:
415 sparse.importfromfiles(repo, opts, pats, force=force)
433 sparse.importfromfiles(repo, opts, importrules, force=force)
416
434
417 if clearrules:
435 if clearrules:
418 sparse.clearrules(repo, force=force)
436 sparse.clearrules(repo, force=force)
419
437
420 if refresh:
438 if refresh:
421 try:
439 try:
422 wlock = repo.wlock()
440 wlock = repo.wlock()
423 fcounts = map(
441 fcounts = map(
424 len,
442 len,
425 sparse.refreshwdir(
443 sparse.refreshwdir(
426 repo, repo.status(), sparse.matcher(repo), force=force
444 repo, repo.status(), sparse.matcher(repo), force=force
427 ),
445 ),
428 )
446 )
429 sparse.printchanges(
447 sparse.printchanges(
430 ui,
448 ui,
431 opts,
449 opts,
432 added=fcounts[0],
450 added=fcounts[0],
433 dropped=fcounts[1],
451 dropped=fcounts[1],
434 conflicting=fcounts[2],
452 conflicting=fcounts[2],
435 )
453 )
436 finally:
454 finally:
437 wlock.release()
455 wlock.release()
@@ -1,843 +1,844 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.update_file(file, p1_tracked=False, wc_tracked=False)
286 dirstate.update_file(file, p1_tracked=False, wc_tracked=False)
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 not type.no_op:
399 if not type.no_op:
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 with repo.dirstate.parentchange():
442 with repo.dirstate.parentchange():
443 mergemod.applyupdates(
443 mergemod.applyupdates(
444 repo,
444 repo,
445 tmresult,
445 tmresult,
446 repo[None],
446 repo[None],
447 repo[b'.'],
447 repo[b'.'],
448 False,
448 False,
449 wantfiledata=False,
449 wantfiledata=False,
450 )
450 )
451
451
452 dirstate = repo.dirstate
452 dirstate = repo.dirstate
453 for file, flags, msg in tmresult.getactions(
453 for file, flags, msg in tmresult.getactions(
454 [mergestatemod.ACTION_GET]
454 [mergestatemod.ACTION_GET]
455 ):
455 ):
456 dirstate.update_file(file, p1_tracked=True, wc_tracked=True)
456 dirstate.update_file(file, p1_tracked=True, wc_tracked=True)
457
457
458 profiles = activeconfig(repo)[2]
458 profiles = activeconfig(repo)[2]
459 changedprofiles = profiles & files
459 changedprofiles = profiles & files
460 # If an active profile changed during the update, refresh the checkout.
460 # If an active profile changed during the update, refresh the checkout.
461 # 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
462 # have been handled by the temporary includes above.
462 # have been handled by the temporary includes above.
463 if changedprofiles and not branchmerge:
463 if changedprofiles and not branchmerge:
464 mf = mctx.manifest()
464 mf = mctx.manifest()
465 for file in mf:
465 for file in mf:
466 old = oldsparsematch(file)
466 old = oldsparsematch(file)
467 new = sparsematch(file)
467 new = sparsematch(file)
468 if not old and new:
468 if not old and new:
469 flags = mf.flags(file)
469 flags = mf.flags(file)
470 prunedactions[file] = (
470 prunedactions[file] = (
471 mergestatemod.ACTION_GET,
471 mergestatemod.ACTION_GET,
472 (flags, False),
472 (flags, False),
473 b'',
473 b'',
474 )
474 )
475 elif old and not new:
475 elif old and not new:
476 prunedactions[file] = (mergestatemod.ACTION_REMOVE, [], b'')
476 prunedactions[file] = (mergestatemod.ACTION_REMOVE, [], b'')
477
477
478 mresult.setactions(prunedactions)
478 mresult.setactions(prunedactions)
479
479
480
480
481 def refreshwdir(repo, origstatus, origsparsematch, force=False):
481 def refreshwdir(repo, origstatus, origsparsematch, force=False):
482 """Refreshes working directory by taking sparse config into account.
482 """Refreshes working directory by taking sparse config into account.
483
483
484 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
485 matcher.
485 matcher.
486
486
487 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
488 unless ``force`` is True.
488 unless ``force`` is True.
489 """
489 """
490 # Verify there are no pending changes
490 # Verify there are no pending changes
491 pending = set()
491 pending = set()
492 pending.update(origstatus.modified)
492 pending.update(origstatus.modified)
493 pending.update(origstatus.added)
493 pending.update(origstatus.added)
494 pending.update(origstatus.removed)
494 pending.update(origstatus.removed)
495 sparsematch = matcher(repo)
495 sparsematch = matcher(repo)
496 abort = False
496 abort = False
497
497
498 for f in pending:
498 for f in pending:
499 if not sparsematch(f):
499 if not sparsematch(f):
500 repo.ui.warn(_(b"pending changes to '%s'\n") % f)
500 repo.ui.warn(_(b"pending changes to '%s'\n") % f)
501 abort = not force
501 abort = not force
502
502
503 if abort:
503 if abort:
504 raise error.Abort(
504 raise error.Abort(
505 _(b'could not update sparseness due to pending changes')
505 _(b'could not update sparseness due to pending changes')
506 )
506 )
507
507
508 # Calculate merge result
508 # Calculate merge result
509 dirstate = repo.dirstate
509 dirstate = repo.dirstate
510 ctx = repo[b'.']
510 ctx = repo[b'.']
511 added = []
511 added = []
512 lookup = []
512 lookup = []
513 dropped = []
513 dropped = []
514 mf = ctx.manifest()
514 mf = ctx.manifest()
515 files = set(mf)
515 files = set(mf)
516 mresult = mergemod.mergeresult()
516 mresult = mergemod.mergeresult()
517
517
518 for file in files:
518 for file in files:
519 old = origsparsematch(file)
519 old = origsparsematch(file)
520 new = sparsematch(file)
520 new = sparsematch(file)
521 # 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
522 # the dirstate yet.
522 # the dirstate yet.
523 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):
524 fl = mf.flags(file)
524 fl = mf.flags(file)
525 if repo.wvfs.exists(file):
525 if repo.wvfs.exists(file):
526 mresult.addfile(file, mergestatemod.ACTION_EXEC, (fl,), b'')
526 mresult.addfile(file, mergestatemod.ACTION_EXEC, (fl,), b'')
527 lookup.append(file)
527 lookup.append(file)
528 else:
528 else:
529 mresult.addfile(
529 mresult.addfile(
530 file, mergestatemod.ACTION_GET, (fl, False), b''
530 file, mergestatemod.ACTION_GET, (fl, False), b''
531 )
531 )
532 added.append(file)
532 added.append(file)
533 # Drop files that are newly excluded, or that still exist in
533 # Drop files that are newly excluded, or that still exist in
534 # the dirstate.
534 # the dirstate.
535 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):
536 dropped.append(file)
536 dropped.append(file)
537 if file not in pending:
537 if file not in pending:
538 mresult.addfile(file, mergestatemod.ACTION_REMOVE, [], b'')
538 mresult.addfile(file, mergestatemod.ACTION_REMOVE, [], b'')
539
539
540 # Verify there are no pending changes in newly included files
540 # Verify there are no pending changes in newly included files
541 abort = False
541 abort = False
542 for file in lookup:
542 for file in lookup:
543 repo.ui.warn(_(b"pending changes to '%s'\n") % file)
543 repo.ui.warn(_(b"pending changes to '%s'\n") % file)
544 abort = not force
544 abort = not force
545 if abort:
545 if abort:
546 raise error.Abort(
546 raise error.Abort(
547 _(
547 _(
548 b'cannot change sparseness due to pending '
548 b'cannot change sparseness due to pending '
549 b'changes (delete the files or use '
549 b'changes (delete the files or use '
550 b'--force to bring them back dirty)'
550 b'--force to bring them back dirty)'
551 )
551 )
552 )
552 )
553
553
554 # Check for files that were only in the dirstate.
554 # Check for files that were only in the dirstate.
555 for file, state in pycompat.iteritems(dirstate):
555 for file, state in pycompat.iteritems(dirstate):
556 if not file in files:
556 if not file in files:
557 old = origsparsematch(file)
557 old = origsparsematch(file)
558 new = sparsematch(file)
558 new = sparsematch(file)
559 if old and not new:
559 if old and not new:
560 dropped.append(file)
560 dropped.append(file)
561
561
562 mergemod.applyupdates(
562 mergemod.applyupdates(
563 repo, mresult, repo[None], repo[b'.'], False, wantfiledata=False
563 repo, mresult, repo[None], repo[b'.'], False, wantfiledata=False
564 )
564 )
565
565
566 # Fix dirstate
566 # Fix dirstate
567 for file in added:
567 for file in added:
568 dirstate.update_file(file, p1_tracked=True, wc_tracked=True)
568 dirstate.update_file(file, p1_tracked=True, wc_tracked=True)
569
569
570 for file in dropped:
570 for file in dropped:
571 dirstate.update_file(file, p1_tracked=False, wc_tracked=False)
571 dirstate.update_file(file, p1_tracked=False, wc_tracked=False)
572
572
573 for file in lookup:
573 for file in lookup:
574 # 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.
575 dirstate.update_file(
575 dirstate.update_file(
576 file, p1_tracked=True, wc_tracked=True, possibly_dirty=True
576 file, p1_tracked=True, wc_tracked=True, possibly_dirty=True
577 )
577 )
578
578
579 return added, dropped, lookup
579 return added, dropped, lookup
580
580
581
581
582 def aftercommit(repo, node):
582 def aftercommit(repo, node):
583 """Perform actions after a working directory commit."""
583 """Perform actions after a working directory commit."""
584 # This function is called unconditionally, even if sparse isn't
584 # This function is called unconditionally, even if sparse isn't
585 # enabled.
585 # enabled.
586 ctx = repo[node]
586 ctx = repo[node]
587
587
588 profiles = patternsforrev(repo, ctx.rev())[2]
588 profiles = patternsforrev(repo, ctx.rev())[2]
589
589
590 # profiles will only have data if sparse is enabled.
590 # profiles will only have data if sparse is enabled.
591 if profiles & set(ctx.files()):
591 if profiles & set(ctx.files()):
592 origstatus = repo.status()
592 origstatus = repo.status()
593 origsparsematch = matcher(repo)
593 origsparsematch = matcher(repo)
594 refreshwdir(repo, origstatus, origsparsematch, force=True)
594 refreshwdir(repo, origstatus, origsparsematch, force=True)
595
595
596 prunetemporaryincludes(repo)
596 prunetemporaryincludes(repo)
597
597
598
598
599 def _updateconfigandrefreshwdir(
599 def _updateconfigandrefreshwdir(
600 repo, includes, excludes, profiles, force=False, removing=False
600 repo, includes, excludes, profiles, force=False, removing=False
601 ):
601 ):
602 """Update the sparse config and working directory state."""
602 """Update the sparse config and working directory state."""
603 with repo.lock():
603 with repo.lock():
604 raw = repo.vfs.tryread(b'sparse')
604 raw = repo.vfs.tryread(b'sparse')
605 oldincludes, oldexcludes, oldprofiles = parseconfig(
605 oldincludes, oldexcludes, oldprofiles = parseconfig(
606 repo.ui, raw, b'sparse'
606 repo.ui, raw, b'sparse'
607 )
607 )
608
608
609 oldstatus = repo.status()
609 oldstatus = repo.status()
610 oldmatch = matcher(repo)
610 oldmatch = matcher(repo)
611 oldrequires = set(repo.requirements)
611 oldrequires = set(repo.requirements)
612
612
613 # TODO remove this try..except once the matcher integrates better
613 # TODO remove this try..except once the matcher integrates better
614 # with dirstate. We currently have to write the updated config
614 # with dirstate. We currently have to write the updated config
615 # because that will invalidate the matcher cache and force a
615 # because that will invalidate the matcher cache and force a
616 # re-read. We ideally want to update the cached matcher on the
616 # re-read. We ideally want to update the cached matcher on the
617 # repo instance then flush the new config to disk once wdir is
617 # repo instance then flush the new config to disk once wdir is
618 # updated. But this requires massive rework to matcher() and its
618 # updated. But this requires massive rework to matcher() and its
619 # consumers.
619 # consumers.
620
620
621 if requirements.SPARSE_REQUIREMENT in oldrequires and removing:
621 if requirements.SPARSE_REQUIREMENT in oldrequires and removing:
622 repo.requirements.discard(requirements.SPARSE_REQUIREMENT)
622 repo.requirements.discard(requirements.SPARSE_REQUIREMENT)
623 scmutil.writereporequirements(repo)
623 scmutil.writereporequirements(repo)
624 elif requirements.SPARSE_REQUIREMENT not in oldrequires:
624 elif requirements.SPARSE_REQUIREMENT not in oldrequires:
625 repo.requirements.add(requirements.SPARSE_REQUIREMENT)
625 repo.requirements.add(requirements.SPARSE_REQUIREMENT)
626 scmutil.writereporequirements(repo)
626 scmutil.writereporequirements(repo)
627
627
628 try:
628 try:
629 writeconfig(repo, includes, excludes, profiles)
629 writeconfig(repo, includes, excludes, profiles)
630 return refreshwdir(repo, oldstatus, oldmatch, force=force)
630 return refreshwdir(repo, oldstatus, oldmatch, force=force)
631 except Exception:
631 except Exception:
632 if repo.requirements != oldrequires:
632 if repo.requirements != oldrequires:
633 repo.requirements.clear()
633 repo.requirements.clear()
634 repo.requirements |= oldrequires
634 repo.requirements |= oldrequires
635 scmutil.writereporequirements(repo)
635 scmutil.writereporequirements(repo)
636 writeconfig(repo, oldincludes, oldexcludes, oldprofiles)
636 writeconfig(repo, oldincludes, oldexcludes, oldprofiles)
637 raise
637 raise
638
638
639
639
640 def clearrules(repo, force=False):
640 def clearrules(repo, force=False):
641 """Clears include/exclude rules from the sparse config.
641 """Clears include/exclude rules from the sparse config.
642
642
643 The remaining sparse config only has profiles, if defined. The working
643 The remaining sparse config only has profiles, if defined. The working
644 directory is refreshed, as needed.
644 directory is refreshed, as needed.
645 """
645 """
646 with repo.wlock(), repo.dirstate.parentchange():
646 with repo.wlock(), repo.dirstate.parentchange():
647 raw = repo.vfs.tryread(b'sparse')
647 raw = repo.vfs.tryread(b'sparse')
648 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
648 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
649
649
650 if not includes and not excludes:
650 if not includes and not excludes:
651 return
651 return
652
652
653 _updateconfigandrefreshwdir(repo, set(), set(), profiles, force=force)
653 _updateconfigandrefreshwdir(repo, set(), set(), profiles, force=force)
654
654
655
655
656 def importfromfiles(repo, opts, paths, force=False):
656 def importfromfiles(repo, opts, paths, force=False):
657 """Import sparse config rules from files.
657 """Import sparse config rules from files.
658
658
659 The updated sparse config is written out and the working directory
659 The updated sparse config is written out and the working directory
660 is refreshed, as needed.
660 is refreshed, as needed.
661 """
661 """
662 with repo.wlock(), repo.dirstate.parentchange():
662 with repo.wlock(), repo.dirstate.parentchange():
663 # read current configuration
663 # read current configuration
664 raw = repo.vfs.tryread(b'sparse')
664 raw = repo.vfs.tryread(b'sparse')
665 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
665 includes, excludes, profiles = parseconfig(repo.ui, raw, b'sparse')
666 aincludes, aexcludes, aprofiles = activeconfig(repo)
666 aincludes, aexcludes, aprofiles = activeconfig(repo)
667
667
668 # Import rules on top; only take in rules that are not yet
668 # Import rules on top; only take in rules that are not yet
669 # part of the active rules.
669 # part of the active rules.
670 changed = False
670 changed = False
671 for p in paths:
671 for p in paths:
672 with util.posixfile(util.expandpath(p), mode=b'rb') as fh:
672 with util.posixfile(util.expandpath(p), mode=b'rb') as fh:
673 raw = fh.read()
673 raw = fh.read()
674
674
675 iincludes, iexcludes, iprofiles = parseconfig(
675 iincludes, iexcludes, iprofiles = parseconfig(
676 repo.ui, raw, b'sparse'
676 repo.ui, raw, b'sparse'
677 )
677 )
678 oldsize = len(includes) + len(excludes) + len(profiles)
678 oldsize = len(includes) + len(excludes) + len(profiles)
679 includes.update(iincludes - aincludes)
679 includes.update(iincludes - aincludes)
680 excludes.update(iexcludes - aexcludes)
680 excludes.update(iexcludes - aexcludes)
681 profiles.update(iprofiles - aprofiles)
681 profiles.update(iprofiles - aprofiles)
682 if len(includes) + len(excludes) + len(profiles) > oldsize:
682 if len(includes) + len(excludes) + len(profiles) > oldsize:
683 changed = True
683 changed = True
684
684
685 profilecount = includecount = excludecount = 0
685 profilecount = includecount = excludecount = 0
686 fcounts = (0, 0, 0)
686 fcounts = (0, 0, 0)
687
687
688 if changed:
688 if changed:
689 profilecount = len(profiles - aprofiles)
689 profilecount = len(profiles - aprofiles)
690 includecount = len(includes - aincludes)
690 includecount = len(includes - aincludes)
691 excludecount = len(excludes - aexcludes)
691 excludecount = len(excludes - aexcludes)
692
692
693 fcounts = map(
693 fcounts = map(
694 len,
694 len,
695 _updateconfigandrefreshwdir(
695 _updateconfigandrefreshwdir(
696 repo, includes, excludes, profiles, force=force
696 repo, includes, excludes, profiles, force=force
697 ),
697 ),
698 )
698 )
699
699
700 printchanges(
700 printchanges(
701 repo.ui, opts, profilecount, includecount, excludecount, *fcounts
701 repo.ui, opts, profilecount, includecount, excludecount, *fcounts
702 )
702 )
703
703
704
704
705 def updateconfig(
705 def updateconfig(
706 repo,
706 repo,
707 pats,
708 opts,
707 opts,
709 include=False,
708 include=(),
710 exclude=False,
709 exclude=(),
711 reset=False,
710 reset=False,
712 delete=False,
711 delete=(),
713 enableprofile=False,
712 enableprofile=(),
714 disableprofile=False,
713 disableprofile=(),
715 force=False,
714 force=False,
716 usereporootpaths=False,
715 usereporootpaths=False,
717 ):
716 ):
718 """Perform a sparse config update.
717 """Perform a sparse config update.
719
718
720 Only one of the actions may be performed.
721
722 The new config is written out and a working directory refresh is performed.
719 The new config is written out and a working directory refresh is performed.
723 """
720 """
724 with repo.wlock(), repo.lock(), repo.dirstate.parentchange():
721 with repo.wlock(), repo.lock(), repo.dirstate.parentchange():
725 raw = repo.vfs.tryread(b'sparse')
722 raw = repo.vfs.tryread(b'sparse')
726 oldinclude, oldexclude, oldprofiles = parseconfig(
723 oldinclude, oldexclude, oldprofiles = parseconfig(
727 repo.ui, raw, b'sparse'
724 repo.ui, raw, b'sparse'
728 )
725 )
729
726
730 if reset:
727 if reset:
731 newinclude = set()
728 newinclude = set()
732 newexclude = set()
729 newexclude = set()
733 newprofiles = set()
730 newprofiles = set()
734 else:
731 else:
735 newinclude = set(oldinclude)
732 newinclude = set(oldinclude)
736 newexclude = set(oldexclude)
733 newexclude = set(oldexclude)
737 newprofiles = set(oldprofiles)
734 newprofiles = set(oldprofiles)
738
735
739 if any(os.path.isabs(pat) for pat in pats):
736 def normalize_pats(pats):
740 raise error.Abort(_(b'paths cannot be absolute'))
737 if any(os.path.isabs(pat) for pat in pats):
738 raise error.Abort(_(b'paths cannot be absolute'))
741
739
742 if not usereporootpaths:
740 if usereporootpaths:
741 return pats
742
743 # let's treat paths as relative to cwd
743 # let's treat paths as relative to cwd
744 root, cwd = repo.root, repo.getcwd()
744 root, cwd = repo.root, repo.getcwd()
745 abspats = []
745 abspats = []
746 for kindpat in pats:
746 for kindpat in pats:
747 kind, pat = matchmod._patsplit(kindpat, None)
747 kind, pat = matchmod._patsplit(kindpat, None)
748 if kind in matchmod.cwdrelativepatternkinds or kind is None:
748 if kind in matchmod.cwdrelativepatternkinds or kind is None:
749 ap = (kind + b':' if kind else b'') + pathutil.canonpath(
749 ap = (kind + b':' if kind else b'') + pathutil.canonpath(
750 root, cwd, pat
750 root, cwd, pat
751 )
751 )
752 abspats.append(ap)
752 abspats.append(ap)
753 else:
753 else:
754 abspats.append(kindpat)
754 abspats.append(kindpat)
755 pats = abspats
755 return abspats
756
756
757 if include:
757 include = normalize_pats(include)
758 newinclude.update(pats)
758 exclude = normalize_pats(exclude)
759 elif exclude:
759 delete = normalize_pats(delete)
760 newexclude.update(pats)
760 disableprofile = normalize_pats(disableprofile)
761 elif enableprofile:
761 enableprofile = normalize_pats(enableprofile)
762 newprofiles.update(pats)
762
763 elif disableprofile:
763 newinclude.difference_update(delete)
764 newprofiles.difference_update(pats)
764 newexclude.difference_update(delete)
765 elif delete:
765 newprofiles.difference_update(disableprofile)
766 newinclude.difference_update(pats)
766 newinclude.update(include)
767 newexclude.difference_update(pats)
767 newprofiles.update(enableprofile)
768 newexclude.update(exclude)
768
769
769 profilecount = len(newprofiles - oldprofiles) - len(
770 profilecount = len(newprofiles - oldprofiles) - len(
770 oldprofiles - newprofiles
771 oldprofiles - newprofiles
771 )
772 )
772 includecount = len(newinclude - oldinclude) - len(
773 includecount = len(newinclude - oldinclude) - len(
773 oldinclude - newinclude
774 oldinclude - newinclude
774 )
775 )
775 excludecount = len(newexclude - oldexclude) - len(
776 excludecount = len(newexclude - oldexclude) - len(
776 oldexclude - newexclude
777 oldexclude - newexclude
777 )
778 )
778
779
779 fcounts = map(
780 fcounts = map(
780 len,
781 len,
781 _updateconfigandrefreshwdir(
782 _updateconfigandrefreshwdir(
782 repo,
783 repo,
783 newinclude,
784 newinclude,
784 newexclude,
785 newexclude,
785 newprofiles,
786 newprofiles,
786 force=force,
787 force=force,
787 removing=reset,
788 removing=reset,
788 ),
789 ),
789 )
790 )
790
791
791 printchanges(
792 printchanges(
792 repo.ui, opts, profilecount, includecount, excludecount, *fcounts
793 repo.ui, opts, profilecount, includecount, excludecount, *fcounts
793 )
794 )
794
795
795
796
796 def printchanges(
797 def printchanges(
797 ui,
798 ui,
798 opts,
799 opts,
799 profilecount=0,
800 profilecount=0,
800 includecount=0,
801 includecount=0,
801 excludecount=0,
802 excludecount=0,
802 added=0,
803 added=0,
803 dropped=0,
804 dropped=0,
804 conflicting=0,
805 conflicting=0,
805 ):
806 ):
806 """Print output summarizing sparse config changes."""
807 """Print output summarizing sparse config changes."""
807 with ui.formatter(b'sparse', opts) as fm:
808 with ui.formatter(b'sparse', opts) as fm:
808 fm.startitem()
809 fm.startitem()
809 fm.condwrite(
810 fm.condwrite(
810 ui.verbose,
811 ui.verbose,
811 b'profiles_added',
812 b'profiles_added',
812 _(b'Profiles changed: %d\n'),
813 _(b'Profiles changed: %d\n'),
813 profilecount,
814 profilecount,
814 )
815 )
815 fm.condwrite(
816 fm.condwrite(
816 ui.verbose,
817 ui.verbose,
817 b'include_rules_added',
818 b'include_rules_added',
818 _(b'Include rules changed: %d\n'),
819 _(b'Include rules changed: %d\n'),
819 includecount,
820 includecount,
820 )
821 )
821 fm.condwrite(
822 fm.condwrite(
822 ui.verbose,
823 ui.verbose,
823 b'exclude_rules_added',
824 b'exclude_rules_added',
824 _(b'Exclude rules changed: %d\n'),
825 _(b'Exclude rules changed: %d\n'),
825 excludecount,
826 excludecount,
826 )
827 )
827
828
828 # In 'plain' verbose mode, mergemod.applyupdates already outputs what
829 # In 'plain' verbose mode, mergemod.applyupdates already outputs what
829 # files are added or removed outside of the templating formatter
830 # files are added or removed outside of the templating formatter
830 # framework. No point in repeating ourselves in that case.
831 # framework. No point in repeating ourselves in that case.
831 if not fm.isplain():
832 if not fm.isplain():
832 fm.condwrite(
833 fm.condwrite(
833 ui.verbose, b'files_added', _(b'Files added: %d\n'), added
834 ui.verbose, b'files_added', _(b'Files added: %d\n'), added
834 )
835 )
835 fm.condwrite(
836 fm.condwrite(
836 ui.verbose, b'files_dropped', _(b'Files dropped: %d\n'), dropped
837 ui.verbose, b'files_dropped', _(b'Files dropped: %d\n'), dropped
837 )
838 )
838 fm.condwrite(
839 fm.condwrite(
839 ui.verbose,
840 ui.verbose,
840 b'files_conflicting',
841 b'files_conflicting',
841 _(b'Files conflicting: %d\n'),
842 _(b'Files conflicting: %d\n'),
842 conflicting,
843 conflicting,
843 )
844 )
@@ -1,80 +1,80 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 >> $HGRCPATH <<EOF
5 $ cat >> $HGRCPATH <<EOF
6 > [extensions]
6 > [extensions]
7 > sparse=
7 > sparse=
8 > purge=
8 > purge=
9 > strip=
9 > strip=
10 > rebase=
10 > rebase=
11 > EOF
11 > EOF
12
12
13 $ echo a > index.html
13 $ echo a > index.html
14 $ echo x > data.py
14 $ echo x > data.py
15 $ echo z > readme.txt
15 $ echo z > readme.txt
16 $ cat > base.sparse <<EOF
16 $ cat > base.sparse <<EOF
17 > [include]
17 > [include]
18 > *.sparse
18 > *.sparse
19 > EOF
19 > EOF
20 $ hg ci -Aqm 'initial'
20 $ hg ci -Aqm 'initial'
21 $ cat > webpage.sparse <<EOF
21 $ cat > webpage.sparse <<EOF
22 > %include base.sparse
22 > %include base.sparse
23 > [include]
23 > [include]
24 > *.html
24 > *.html
25 > EOF
25 > EOF
26 $ hg ci -Aqm 'initial'
26 $ hg ci -Aqm 'initial'
27
27
28 Clear rules when there are includes
28 Clear rules when there are includes
29
29
30 $ hg debugsparse --include *.py
30 $ hg debugsparse --include *.py
31 $ ls -A
31 $ ls -A
32 .hg
32 .hg
33 data.py
33 data.py
34 $ hg debugsparse --clear-rules
34 $ hg debugsparse --clear-rules
35 $ ls -A
35 $ ls -A
36 .hg
36 .hg
37 base.sparse
37 base.sparse
38 data.py
38 data.py
39 index.html
39 index.html
40 readme.txt
40 readme.txt
41 webpage.sparse
41 webpage.sparse
42
42
43 Clear rules when there are excludes
43 Clear rules when there are excludes
44
44
45 $ hg debugsparse --exclude *.sparse
45 $ hg debugsparse -X base.sparse -X webpage.sparse
46 $ ls -A
46 $ ls -A
47 .hg
47 .hg
48 data.py
48 data.py
49 index.html
49 index.html
50 readme.txt
50 readme.txt
51 $ hg debugsparse --clear-rules
51 $ hg debugsparse --clear-rules
52 $ ls -A
52 $ ls -A
53 .hg
53 .hg
54 base.sparse
54 base.sparse
55 data.py
55 data.py
56 index.html
56 index.html
57 readme.txt
57 readme.txt
58 webpage.sparse
58 webpage.sparse
59
59
60 Clearing rules should not alter profiles
60 Clearing rules should not alter profiles
61
61
62 $ hg debugsparse --enable-profile webpage.sparse
62 $ hg debugsparse --enable-profile webpage.sparse
63 $ ls -A
63 $ ls -A
64 .hg
64 .hg
65 base.sparse
65 base.sparse
66 index.html
66 index.html
67 webpage.sparse
67 webpage.sparse
68 $ hg debugsparse --include *.py
68 $ hg debugsparse --include *.py
69 $ ls -A
69 $ ls -A
70 .hg
70 .hg
71 base.sparse
71 base.sparse
72 data.py
72 data.py
73 index.html
73 index.html
74 webpage.sparse
74 webpage.sparse
75 $ hg debugsparse --clear-rules
75 $ hg debugsparse --clear-rules
76 $ ls -A
76 $ ls -A
77 .hg
77 .hg
78 base.sparse
78 base.sparse
79 index.html
79 index.html
80 webpage.sparse
80 webpage.sparse
@@ -1,431 +1,440 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 -f --delete '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 -f --exclude '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 Multiple -I and -X can be passed at once
258
259 $ hg debugsparse --reset -I '*2' -X 'hide2'
260 $ ls -A
261 .hg
262 hide.orig
263 show2
264 $ hg debugsparse --reset -X 'hide*'
265
257 Verify strip -k resets dirstate correctly
266 Verify strip -k resets dirstate correctly
258
267
259 $ hg status
268 $ hg status
260 $ hg debugsparse
269 $ hg debugsparse
261 [exclude]
270 [exclude]
262 hide*
271 hide*
263
272
264 $ hg log -r . -T '{rev}\n' --stat
273 $ hg log -r . -T '{rev}\n' --stat
265 1
274 1
266 hide | 2 +-
275 hide | 2 +-
267 hide2 | 1 +
276 hide2 | 1 +
268 show | 2 +-
277 show | 2 +-
269 show2 | 1 +
278 show2 | 1 +
270 4 files changed, 4 insertions(+), 2 deletions(-)
279 4 files changed, 4 insertions(+), 2 deletions(-)
271
280
272 $ hg strip -r . -k
281 $ hg strip -r . -k
273 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/39278f7c08a9-ce59e002-backup.hg
282 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/39278f7c08a9-ce59e002-backup.hg
274 $ hg status
283 $ hg status
275 M show
284 M show
276 ? show2
285 ? show2
277
286
278 Verify rebase succeeds if all changed files are in sparse checkout
287 Verify rebase succeeds if all changed files are in sparse checkout
279
288
280 $ hg commit -Aqm "add show2"
289 $ hg commit -Aqm "add show2"
281 $ hg rebase -d 1 --config extensions.rebase=
290 $ hg rebase -d 1 --config extensions.rebase=
282 rebasing 2:bdde55290160 tip "add show2"
291 rebasing 2:bdde55290160 tip "add show2"
283 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/bdde55290160-216ed9c6-rebase.hg
292 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/bdde55290160-216ed9c6-rebase.hg
284
293
285 Verify log --sparse only shows commits that affect the sparse checkout
294 Verify log --sparse only shows commits that affect the sparse checkout
286
295
287 $ hg log -T '{rev} '
296 $ hg log -T '{rev} '
288 2 1 0 (no-eol)
297 2 1 0 (no-eol)
289 $ hg log --sparse -T '{rev} '
298 $ hg log --sparse -T '{rev} '
290 2 0 (no-eol)
299 2 0 (no-eol)
291
300
292 Test status on a file in a subdir
301 Test status on a file in a subdir
293
302
294 $ mkdir -p dir1/dir2
303 $ mkdir -p dir1/dir2
295 $ touch dir1/dir2/file
304 $ touch dir1/dir2/file
296 $ hg debugsparse -I dir1/dir2
305 $ hg debugsparse -I dir1/dir2
297 $ hg status
306 $ hg status
298 ? dir1/dir2/file
307 ? dir1/dir2/file
299
308
300 Mix files and subdirectories, both "glob:" and unprefixed
309 Mix files and subdirectories, both "glob:" and unprefixed
301
310
302 $ hg debugsparse --reset
311 $ hg debugsparse --reset
303 $ touch dir1/notshown
312 $ touch dir1/notshown
304 $ hg commit -A dir1/notshown -m "notshown"
313 $ hg commit -A dir1/notshown -m "notshown"
305 $ hg debugsparse --include 'dir1/dir2'
314 $ hg debugsparse --include 'dir1/dir2'
306 $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
315 $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
307 ./
316 ./
308 ./dir1/
317 ./dir1/
309 ./dir1/dir2/
318 ./dir1/dir2/
310 ./dir1/dir2/file
319 ./dir1/dir2/file
311 ./hide.orig
320 ./hide.orig
312 $ hg debugsparse --delete 'dir1/dir2'
321 $ hg debugsparse --delete 'dir1/dir2'
313 $ hg debugsparse --include 'glob:dir1/dir2'
322 $ hg debugsparse --include 'glob:dir1/dir2'
314 $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
323 $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
315 ./
324 ./
316 ./dir1/
325 ./dir1/
317 ./dir1/dir2/
326 ./dir1/dir2/
318 ./dir1/dir2/file
327 ./dir1/dir2/file
319 ./hide.orig
328 ./hide.orig
320
329
321 Test that add -s adds dirs to sparse profile
330 Test that add -s adds dirs to sparse profile
322
331
323 $ hg debugsparse --reset
332 $ hg debugsparse --reset
324 $ hg debugsparse --include empty
333 $ hg debugsparse --include empty
325 $ hg debugsparse
334 $ hg debugsparse
326 [include]
335 [include]
327 empty
336 empty
328
337
329
338
330 $ mkdir add
339 $ mkdir add
331 $ touch add/foo
340 $ touch add/foo
332 $ touch add/bar
341 $ touch add/bar
333 $ hg add add/foo
342 $ hg add add/foo
334 abort: cannot add 'add/foo' - it is outside the sparse checkout
343 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)
344 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
336 [255]
345 [255]
337 $ hg add -s add/foo
346 $ hg add -s add/foo
338 $ hg st
347 $ hg st
339 A add/foo
348 A add/foo
340 ? add/bar
349 ? add/bar
341 $ hg debugsparse
350 $ hg debugsparse
342 [include]
351 [include]
343 add
352 add
344 empty
353 empty
345
354
346 $ hg add -s add/*
355 $ hg add -s add/*
347 add/foo already tracked!
356 add/foo already tracked!
348 $ hg st
357 $ hg st
349 A add/bar
358 A add/bar
350 A add/foo
359 A add/foo
351 $ hg debugsparse
360 $ hg debugsparse
352 [include]
361 [include]
353 add
362 add
354 empty
363 empty
355
364
356
365
357 $ cd ..
366 $ cd ..
358
367
359 Test non-sparse repos work while sparse is loaded
368 Test non-sparse repos work while sparse is loaded
360 $ hg init sparserepo
369 $ hg init sparserepo
361 $ hg init nonsparserepo
370 $ hg init nonsparserepo
362 $ cd sparserepo
371 $ cd sparserepo
363 $ cat > .hg/hgrc <<EOF
372 $ cat > .hg/hgrc <<EOF
364 > [extensions]
373 > [extensions]
365 > sparse=
374 > sparse=
366 > EOF
375 > EOF
367 $ cd ../nonsparserepo
376 $ cd ../nonsparserepo
368 $ echo x > x && hg add x && hg commit -qAm x
377 $ echo x > x && hg add x && hg commit -qAm x
369 $ cd ../sparserepo
378 $ cd ../sparserepo
370 $ hg clone ../nonsparserepo ../nonsparserepo2
379 $ hg clone ../nonsparserepo ../nonsparserepo2
371 updating to branch default
380 updating to branch default
372 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
373
382
374 Test debugrebuilddirstate
383 Test debugrebuilddirstate
375 $ cd ../sparserepo
384 $ cd ../sparserepo
376 $ touch included
385 $ touch included
377 $ touch excluded
386 $ touch excluded
378 $ hg add included excluded
387 $ hg add included excluded
379 $ hg commit -m 'a commit' -q
388 $ hg commit -m 'a commit' -q
380 $ cp .hg/dirstate ../dirstateboth
389 $ cp .hg/dirstate ../dirstateboth
381 $ hg debugsparse -X excluded
390 $ hg debugsparse -X excluded
382 $ cp ../dirstateboth .hg/dirstate
391 $ cp ../dirstateboth .hg/dirstate
383 $ hg debugrebuilddirstate
392 $ hg debugrebuilddirstate
384 $ hg debugdirstate
393 $ hg debugdirstate
385 n 0 -1 unset included
394 n 0 -1 unset included
386
395
387 Test debugdirstate --minimal where file is in the parent manifest but not the
396 Test debugdirstate --minimal where file is in the parent manifest but not the
388 dirstate
397 dirstate
389 $ hg debugsparse -X included
398 $ hg debugsparse -X included
390 $ hg debugdirstate
399 $ hg debugdirstate
391 $ cp .hg/dirstate ../dirstateallexcluded
400 $ cp .hg/dirstate ../dirstateallexcluded
392 $ hg debugsparse --reset
401 $ hg debugsparse --reset
393 $ hg debugsparse -X excluded
402 $ hg debugsparse -X excluded
394 $ cp ../dirstateallexcluded .hg/dirstate
403 $ cp ../dirstateallexcluded .hg/dirstate
395 $ touch includedadded
404 $ touch includedadded
396 $ hg add includedadded
405 $ hg add includedadded
397 $ hg debugdirstate --no-dates
406 $ hg debugdirstate --no-dates
398 a 0 -1 unset includedadded
407 a 0 -1 unset includedadded
399 $ hg debugrebuilddirstate --minimal
408 $ hg debugrebuilddirstate --minimal
400 $ hg debugdirstate --no-dates
409 $ hg debugdirstate --no-dates
401 n 0 -1 unset included
410 n 0 -1 unset included
402 a 0 -1 * includedadded (glob)
411 a 0 -1 * includedadded (glob)
403
412
404 Test debugdirstate --minimal where a file is not in parent manifest
413 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
414 but in the dirstate. This should take into account excluded files in the
406 manifest
415 manifest
407 $ cp ../dirstateboth .hg/dirstate
416 $ cp ../dirstateboth .hg/dirstate
408 $ touch includedadded
417 $ touch includedadded
409 $ hg add includedadded
418 $ hg add includedadded
410 $ touch excludednomanifest
419 $ touch excludednomanifest
411 $ hg add excludednomanifest
420 $ hg add excludednomanifest
412 $ cp .hg/dirstate ../moreexcluded
421 $ cp .hg/dirstate ../moreexcluded
413 $ hg forget excludednomanifest
422 $ hg forget excludednomanifest
414 $ rm excludednomanifest
423 $ rm excludednomanifest
415 $ hg debugsparse -X excludednomanifest
424 $ hg debugsparse -X excludednomanifest
416 $ cp ../moreexcluded .hg/dirstate
425 $ cp ../moreexcluded .hg/dirstate
417 $ hg manifest
426 $ hg manifest
418 excluded
427 excluded
419 included
428 included
420 We have files in the dirstate that are included and excluded. Some are in the
429 We have files in the dirstate that are included and excluded. Some are in the
421 manifest and some are not.
430 manifest and some are not.
422 $ hg debugdirstate --no-dates
431 $ hg debugdirstate --no-dates
423 n * excluded (glob)
432 n * excluded (glob)
424 a * excludednomanifest (glob)
433 a * excludednomanifest (glob)
425 n * included (glob)
434 n * included (glob)
426 a * includedadded (glob)
435 a * includedadded (glob)
427 $ hg debugrebuilddirstate --minimal
436 $ hg debugrebuilddirstate --minimal
428 $ hg debugdirstate --no-dates
437 $ hg debugdirstate --no-dates
429 n * included (glob)
438 n * included (glob)
430 a * includedadded (glob)
439 a * includedadded (glob)
431
440
General Comments 0
You need to be logged in to leave comments. Login now