##// END OF EJS Templates
sparse: rename command to debugsparse...
Gregory Szorc -
r33293:c9cbf4de default
parent child Browse files
Show More
@@ -1,1077 +1,1077 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
21
22 from __future__ import absolute_import
22 from __future__ import absolute_import
23
23
24 import collections
24 import collections
25 import hashlib
25 import hashlib
26 import os
26 import os
27
27
28 from mercurial.i18n import _
28 from mercurial.i18n import _
29 from mercurial.node import nullid
29 from mercurial.node import nullid
30 from mercurial import (
30 from mercurial import (
31 cmdutil,
31 cmdutil,
32 commands,
32 commands,
33 context,
33 context,
34 dirstate,
34 dirstate,
35 error,
35 error,
36 extensions,
36 extensions,
37 hg,
37 hg,
38 localrepo,
38 localrepo,
39 match as matchmod,
39 match as matchmod,
40 merge as mergemod,
40 merge as mergemod,
41 registrar,
41 registrar,
42 util,
42 util,
43 )
43 )
44
44
45 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
45 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
46 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
46 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
47 # be specifying the version(s) of Mercurial they are tested with, or
47 # be specifying the version(s) of Mercurial they are tested with, or
48 # leave the attribute unspecified.
48 # leave the attribute unspecified.
49 testedwith = 'ships-with-hg-core'
49 testedwith = 'ships-with-hg-core'
50
50
51 cmdtable = {}
51 cmdtable = {}
52 command = registrar.command(cmdtable)
52 command = registrar.command(cmdtable)
53
53
54 def uisetup(ui):
54 def uisetup(ui):
55 _setupupdates(ui)
55 _setupupdates(ui)
56 _setupcommit(ui)
56 _setupcommit(ui)
57
57
58 def extsetup(ui):
58 def extsetup(ui):
59 _setupclone(ui)
59 _setupclone(ui)
60 _setuplog(ui)
60 _setuplog(ui)
61 _setupadd(ui)
61 _setupadd(ui)
62 _setupdirstate(ui)
62 _setupdirstate(ui)
63 # if fsmonitor is enabled, tell it to use our hash function
63 # if fsmonitor is enabled, tell it to use our hash function
64 try:
64 try:
65 fsmonitor = extensions.find('fsmonitor')
65 fsmonitor = extensions.find('fsmonitor')
66 def _hashignore(orig, ignore):
66 def _hashignore(orig, ignore):
67 return _hashmatcher(ignore)
67 return _hashmatcher(ignore)
68 extensions.wrapfunction(fsmonitor, '_hashignore', _hashignore)
68 extensions.wrapfunction(fsmonitor, '_hashignore', _hashignore)
69 except KeyError:
69 except KeyError:
70 pass
70 pass
71
71
72 def reposetup(ui, repo):
72 def reposetup(ui, repo):
73 if not util.safehasattr(repo, 'dirstate'):
73 if not util.safehasattr(repo, 'dirstate'):
74 return
74 return
75
75
76 _wraprepo(ui, repo)
76 _wraprepo(ui, repo)
77
77
78 def replacefilecache(cls, propname, replacement):
78 def replacefilecache(cls, propname, replacement):
79 """Replace a filecache property with a new class. This allows changing the
79 """Replace a filecache property with a new class. This allows changing the
80 cache invalidation condition."""
80 cache invalidation condition."""
81 origcls = cls
81 origcls = cls
82 assert callable(replacement)
82 assert callable(replacement)
83 while cls is not object:
83 while cls is not object:
84 if propname in cls.__dict__:
84 if propname in cls.__dict__:
85 orig = cls.__dict__[propname]
85 orig = cls.__dict__[propname]
86 setattr(cls, propname, replacement(orig))
86 setattr(cls, propname, replacement(orig))
87 break
87 break
88 cls = cls.__bases__[0]
88 cls = cls.__bases__[0]
89
89
90 if cls is object:
90 if cls is object:
91 raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
91 raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
92 propname))
92 propname))
93
93
94 def _setupupdates(ui):
94 def _setupupdates(ui):
95 def _calculateupdates(orig, repo, wctx, mctx, ancestors, branchmerge, *arg,
95 def _calculateupdates(orig, repo, wctx, mctx, ancestors, branchmerge, *arg,
96 **kwargs):
96 **kwargs):
97 """Filter updates to only lay out files that match the sparse rules.
97 """Filter updates to only lay out files that match the sparse rules.
98 """
98 """
99 actions, diverge, renamedelete = orig(repo, wctx, mctx, ancestors,
99 actions, diverge, renamedelete = orig(repo, wctx, mctx, ancestors,
100 branchmerge, *arg, **kwargs)
100 branchmerge, *arg, **kwargs)
101
101
102 if not util.safehasattr(repo, 'sparsematch'):
102 if not util.safehasattr(repo, 'sparsematch'):
103 return actions, diverge, renamedelete
103 return actions, diverge, renamedelete
104
104
105 files = set()
105 files = set()
106 prunedactions = {}
106 prunedactions = {}
107 oldrevs = [pctx.rev() for pctx in wctx.parents()]
107 oldrevs = [pctx.rev() for pctx in wctx.parents()]
108 oldsparsematch = repo.sparsematch(*oldrevs)
108 oldsparsematch = repo.sparsematch(*oldrevs)
109
109
110 if branchmerge:
110 if branchmerge:
111 # If we're merging, use the wctx filter, since we're merging into
111 # If we're merging, use the wctx filter, since we're merging into
112 # the wctx.
112 # the wctx.
113 sparsematch = repo.sparsematch(wctx.parents()[0].rev())
113 sparsematch = repo.sparsematch(wctx.parents()[0].rev())
114 else:
114 else:
115 # If we're updating, use the target context's filter, since we're
115 # If we're updating, use the target context's filter, since we're
116 # moving to the target context.
116 # moving to the target context.
117 sparsematch = repo.sparsematch(mctx.rev())
117 sparsematch = repo.sparsematch(mctx.rev())
118
118
119 temporaryfiles = []
119 temporaryfiles = []
120 for file, action in actions.iteritems():
120 for file, action in actions.iteritems():
121 type, args, msg = action
121 type, args, msg = action
122 files.add(file)
122 files.add(file)
123 if sparsematch(file):
123 if sparsematch(file):
124 prunedactions[file] = action
124 prunedactions[file] = action
125 elif type == 'm':
125 elif type == 'm':
126 temporaryfiles.append(file)
126 temporaryfiles.append(file)
127 prunedactions[file] = action
127 prunedactions[file] = action
128 elif branchmerge:
128 elif branchmerge:
129 if type != 'k':
129 if type != 'k':
130 temporaryfiles.append(file)
130 temporaryfiles.append(file)
131 prunedactions[file] = action
131 prunedactions[file] = action
132 elif type == 'f':
132 elif type == 'f':
133 prunedactions[file] = action
133 prunedactions[file] = action
134 elif file in wctx:
134 elif file in wctx:
135 prunedactions[file] = ('r', args, msg)
135 prunedactions[file] = ('r', args, msg)
136
136
137 if len(temporaryfiles) > 0:
137 if len(temporaryfiles) > 0:
138 ui.status(_("temporarily included %d file(s) in the sparse checkout"
138 ui.status(_("temporarily included %d file(s) in the sparse checkout"
139 " for merging\n") % len(temporaryfiles))
139 " for merging\n") % len(temporaryfiles))
140 repo.addtemporaryincludes(temporaryfiles)
140 repo.addtemporaryincludes(temporaryfiles)
141
141
142 # Add the new files to the working copy so they can be merged, etc
142 # Add the new files to the working copy so they can be merged, etc
143 actions = []
143 actions = []
144 message = 'temporarily adding to sparse checkout'
144 message = 'temporarily adding to sparse checkout'
145 wctxmanifest = repo[None].manifest()
145 wctxmanifest = repo[None].manifest()
146 for file in temporaryfiles:
146 for file in temporaryfiles:
147 if file in wctxmanifest:
147 if file in wctxmanifest:
148 fctx = repo[None][file]
148 fctx = repo[None][file]
149 actions.append((file, (fctx.flags(), False), message))
149 actions.append((file, (fctx.flags(), False), message))
150
150
151 typeactions = collections.defaultdict(list)
151 typeactions = collections.defaultdict(list)
152 typeactions['g'] = actions
152 typeactions['g'] = actions
153 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'],
153 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'],
154 False)
154 False)
155
155
156 dirstate = repo.dirstate
156 dirstate = repo.dirstate
157 for file, flags, msg in actions:
157 for file, flags, msg in actions:
158 dirstate.normal(file)
158 dirstate.normal(file)
159
159
160 profiles = repo.getactiveprofiles()
160 profiles = repo.getactiveprofiles()
161 changedprofiles = profiles & files
161 changedprofiles = profiles & files
162 # If an active profile changed during the update, refresh the checkout.
162 # If an active profile changed during the update, refresh the checkout.
163 # Don't do this during a branch merge, since all incoming changes should
163 # Don't do this during a branch merge, since all incoming changes should
164 # have been handled by the temporary includes above.
164 # have been handled by the temporary includes above.
165 if changedprofiles and not branchmerge:
165 if changedprofiles and not branchmerge:
166 mf = mctx.manifest()
166 mf = mctx.manifest()
167 for file in mf:
167 for file in mf:
168 old = oldsparsematch(file)
168 old = oldsparsematch(file)
169 new = sparsematch(file)
169 new = sparsematch(file)
170 if not old and new:
170 if not old and new:
171 flags = mf.flags(file)
171 flags = mf.flags(file)
172 prunedactions[file] = ('g', (flags, False), '')
172 prunedactions[file] = ('g', (flags, False), '')
173 elif old and not new:
173 elif old and not new:
174 prunedactions[file] = ('r', [], '')
174 prunedactions[file] = ('r', [], '')
175
175
176 return prunedactions, diverge, renamedelete
176 return prunedactions, diverge, renamedelete
177
177
178 extensions.wrapfunction(mergemod, 'calculateupdates', _calculateupdates)
178 extensions.wrapfunction(mergemod, 'calculateupdates', _calculateupdates)
179
179
180 def _update(orig, repo, node, branchmerge, *args, **kwargs):
180 def _update(orig, repo, node, branchmerge, *args, **kwargs):
181 results = orig(repo, node, branchmerge, *args, **kwargs)
181 results = orig(repo, node, branchmerge, *args, **kwargs)
182
182
183 # If we're updating to a location, clean up any stale temporary includes
183 # If we're updating to a location, clean up any stale temporary includes
184 # (ex: this happens during hg rebase --abort).
184 # (ex: this happens during hg rebase --abort).
185 if not branchmerge and util.safehasattr(repo, 'sparsematch'):
185 if not branchmerge and util.safehasattr(repo, 'sparsematch'):
186 repo.prunetemporaryincludes()
186 repo.prunetemporaryincludes()
187 return results
187 return results
188
188
189 extensions.wrapfunction(mergemod, 'update', _update)
189 extensions.wrapfunction(mergemod, 'update', _update)
190
190
191 def _setupcommit(ui):
191 def _setupcommit(ui):
192 def _refreshoncommit(orig, self, node):
192 def _refreshoncommit(orig, self, node):
193 """Refresh the checkout when commits touch .hgsparse
193 """Refresh the checkout when commits touch .hgsparse
194 """
194 """
195 orig(self, node)
195 orig(self, node)
196 repo = self._repo
196 repo = self._repo
197 if util.safehasattr(repo, 'getsparsepatterns'):
197 if util.safehasattr(repo, 'getsparsepatterns'):
198 ctx = repo[node]
198 ctx = repo[node]
199 _, _, profiles = repo.getsparsepatterns(ctx.rev())
199 _, _, profiles = repo.getsparsepatterns(ctx.rev())
200 if set(profiles) & set(ctx.files()):
200 if set(profiles) & set(ctx.files()):
201 origstatus = repo.status()
201 origstatus = repo.status()
202 origsparsematch = repo.sparsematch()
202 origsparsematch = repo.sparsematch()
203 _refresh(repo.ui, repo, origstatus, origsparsematch, True)
203 _refresh(repo.ui, repo, origstatus, origsparsematch, True)
204
204
205 repo.prunetemporaryincludes()
205 repo.prunetemporaryincludes()
206
206
207 extensions.wrapfunction(context.committablectx, 'markcommitted',
207 extensions.wrapfunction(context.committablectx, 'markcommitted',
208 _refreshoncommit)
208 _refreshoncommit)
209
209
210 def _setuplog(ui):
210 def _setuplog(ui):
211 entry = commands.table['^log|history']
211 entry = commands.table['^log|history']
212 entry[1].append(('', 'sparse', None,
212 entry[1].append(('', 'sparse', None,
213 "limit to changesets affecting the sparse checkout"))
213 "limit to changesets affecting the sparse checkout"))
214
214
215 def _logrevs(orig, repo, opts):
215 def _logrevs(orig, repo, opts):
216 revs = orig(repo, opts)
216 revs = orig(repo, opts)
217 if opts.get('sparse'):
217 if opts.get('sparse'):
218 sparsematch = repo.sparsematch()
218 sparsematch = repo.sparsematch()
219 def ctxmatch(rev):
219 def ctxmatch(rev):
220 ctx = repo[rev]
220 ctx = repo[rev]
221 return any(f for f in ctx.files() if sparsematch(f))
221 return any(f for f in ctx.files() if sparsematch(f))
222 revs = revs.filter(ctxmatch)
222 revs = revs.filter(ctxmatch)
223 return revs
223 return revs
224 extensions.wrapfunction(cmdutil, '_logrevs', _logrevs)
224 extensions.wrapfunction(cmdutil, '_logrevs', _logrevs)
225
225
226 def _clonesparsecmd(orig, ui, repo, *args, **opts):
226 def _clonesparsecmd(orig, ui, repo, *args, **opts):
227 include_pat = opts.get('include')
227 include_pat = opts.get('include')
228 exclude_pat = opts.get('exclude')
228 exclude_pat = opts.get('exclude')
229 enableprofile_pat = opts.get('enable_profile')
229 enableprofile_pat = opts.get('enable_profile')
230 include = exclude = enableprofile = False
230 include = exclude = enableprofile = False
231 if include_pat:
231 if include_pat:
232 pat = include_pat
232 pat = include_pat
233 include = True
233 include = True
234 if exclude_pat:
234 if exclude_pat:
235 pat = exclude_pat
235 pat = exclude_pat
236 exclude = True
236 exclude = True
237 if enableprofile_pat:
237 if enableprofile_pat:
238 pat = enableprofile_pat
238 pat = enableprofile_pat
239 enableprofile = True
239 enableprofile = True
240 if sum([include, exclude, enableprofile]) > 1:
240 if sum([include, exclude, enableprofile]) > 1:
241 raise error.Abort(_("too many flags specified."))
241 raise error.Abort(_("too many flags specified."))
242 if include or exclude or enableprofile:
242 if include or exclude or enableprofile:
243 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
243 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
244 _config(self.ui, self.unfiltered(), pat, {}, include=include,
244 _config(self.ui, self.unfiltered(), pat, {}, include=include,
245 exclude=exclude, enableprofile=enableprofile)
245 exclude=exclude, enableprofile=enableprofile)
246 return orig(self, node, overwrite, *args, **kwargs)
246 return orig(self, node, overwrite, *args, **kwargs)
247 extensions.wrapfunction(hg, 'updaterepo', clonesparse)
247 extensions.wrapfunction(hg, 'updaterepo', clonesparse)
248 return orig(ui, repo, *args, **opts)
248 return orig(ui, repo, *args, **opts)
249
249
250 def _setupclone(ui):
250 def _setupclone(ui):
251 entry = commands.table['^clone']
251 entry = commands.table['^clone']
252 entry[1].append(('', 'enable-profile', [],
252 entry[1].append(('', 'enable-profile', [],
253 'enable a sparse profile'))
253 'enable a sparse profile'))
254 entry[1].append(('', 'include', [],
254 entry[1].append(('', 'include', [],
255 'include sparse pattern'))
255 'include sparse pattern'))
256 entry[1].append(('', 'exclude', [],
256 entry[1].append(('', 'exclude', [],
257 'exclude sparse pattern'))
257 'exclude sparse pattern'))
258 extensions.wrapcommand(commands.table, 'clone', _clonesparsecmd)
258 extensions.wrapcommand(commands.table, 'clone', _clonesparsecmd)
259
259
260 def _setupadd(ui):
260 def _setupadd(ui):
261 entry = commands.table['^add']
261 entry = commands.table['^add']
262 entry[1].append(('s', 'sparse', None,
262 entry[1].append(('s', 'sparse', None,
263 'also include directories of added files in sparse config'))
263 'also include directories of added files in sparse config'))
264
264
265 def _add(orig, ui, repo, *pats, **opts):
265 def _add(orig, ui, repo, *pats, **opts):
266 if opts.get('sparse'):
266 if opts.get('sparse'):
267 dirs = set()
267 dirs = set()
268 for pat in pats:
268 for pat in pats:
269 dirname, basename = util.split(pat)
269 dirname, basename = util.split(pat)
270 dirs.add(dirname)
270 dirs.add(dirname)
271 _config(ui, repo, list(dirs), opts, include=True)
271 _config(ui, repo, list(dirs), opts, include=True)
272 return orig(ui, repo, *pats, **opts)
272 return orig(ui, repo, *pats, **opts)
273
273
274 extensions.wrapcommand(commands.table, 'add', _add)
274 extensions.wrapcommand(commands.table, 'add', _add)
275
275
276 def _setupdirstate(ui):
276 def _setupdirstate(ui):
277 """Modify the dirstate to prevent stat'ing excluded files,
277 """Modify the dirstate to prevent stat'ing excluded files,
278 and to prevent modifications to files outside the checkout.
278 and to prevent modifications to files outside the checkout.
279 """
279 """
280
280
281 def _dirstate(orig, repo):
281 def _dirstate(orig, repo):
282 dirstate = orig(repo)
282 dirstate = orig(repo)
283 dirstate.repo = repo
283 dirstate.repo = repo
284 return dirstate
284 return dirstate
285 extensions.wrapfunction(
285 extensions.wrapfunction(
286 localrepo.localrepository.dirstate, 'func', _dirstate)
286 localrepo.localrepository.dirstate, 'func', _dirstate)
287
287
288 # The atrocity below is needed to wrap dirstate._ignore. It is a cached
288 # The atrocity below is needed to wrap dirstate._ignore. It is a cached
289 # property, which means normal function wrapping doesn't work.
289 # property, which means normal function wrapping doesn't work.
290 class ignorewrapper(object):
290 class ignorewrapper(object):
291 def __init__(self, orig):
291 def __init__(self, orig):
292 self.orig = orig
292 self.orig = orig
293 self.origignore = None
293 self.origignore = None
294 self.func = None
294 self.func = None
295 self.sparsematch = None
295 self.sparsematch = None
296
296
297 def __get__(self, obj, type=None):
297 def __get__(self, obj, type=None):
298 repo = obj.repo
298 repo = obj.repo
299 origignore = self.orig.__get__(obj)
299 origignore = self.orig.__get__(obj)
300 if not util.safehasattr(repo, 'sparsematch'):
300 if not util.safehasattr(repo, 'sparsematch'):
301 return origignore
301 return origignore
302
302
303 sparsematch = repo.sparsematch()
303 sparsematch = repo.sparsematch()
304 if self.sparsematch != sparsematch or self.origignore != origignore:
304 if self.sparsematch != sparsematch or self.origignore != origignore:
305 self.func = unionmatcher([origignore,
305 self.func = unionmatcher([origignore,
306 negatematcher(sparsematch)])
306 negatematcher(sparsematch)])
307 self.sparsematch = sparsematch
307 self.sparsematch = sparsematch
308 self.origignore = origignore
308 self.origignore = origignore
309 return self.func
309 return self.func
310
310
311 def __set__(self, obj, value):
311 def __set__(self, obj, value):
312 return self.orig.__set__(obj, value)
312 return self.orig.__set__(obj, value)
313
313
314 def __delete__(self, obj):
314 def __delete__(self, obj):
315 return self.orig.__delete__(obj)
315 return self.orig.__delete__(obj)
316
316
317 replacefilecache(dirstate.dirstate, '_ignore', ignorewrapper)
317 replacefilecache(dirstate.dirstate, '_ignore', ignorewrapper)
318
318
319 # dirstate.rebuild should not add non-matching files
319 # dirstate.rebuild should not add non-matching files
320 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
320 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
321 if util.safehasattr(self.repo, 'sparsematch'):
321 if util.safehasattr(self.repo, 'sparsematch'):
322 matcher = self.repo.sparsematch()
322 matcher = self.repo.sparsematch()
323 allfiles = allfiles.matches(matcher)
323 allfiles = allfiles.matches(matcher)
324 if changedfiles:
324 if changedfiles:
325 changedfiles = [f for f in changedfiles if matcher(f)]
325 changedfiles = [f for f in changedfiles if matcher(f)]
326
326
327 if changedfiles is not None:
327 if changedfiles is not None:
328 # In _rebuild, these files will be deleted from the dirstate
328 # In _rebuild, these files will be deleted from the dirstate
329 # when they are not found to be in allfiles
329 # when they are not found to be in allfiles
330 dirstatefilestoremove = set(f for f in self if not matcher(f))
330 dirstatefilestoremove = set(f for f in self if not matcher(f))
331 changedfiles = dirstatefilestoremove.union(changedfiles)
331 changedfiles = dirstatefilestoremove.union(changedfiles)
332
332
333 return orig(self, parent, allfiles, changedfiles)
333 return orig(self, parent, allfiles, changedfiles)
334 extensions.wrapfunction(dirstate.dirstate, 'rebuild', _rebuild)
334 extensions.wrapfunction(dirstate.dirstate, 'rebuild', _rebuild)
335
335
336 # Prevent adding files that are outside the sparse checkout
336 # Prevent adding files that are outside the sparse checkout
337 editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
337 editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
338 hint = _('include file with `hg sparse --include <pattern>` or use ' +
338 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
339 '`hg add -s <file>` to include file directory while adding')
339 '`hg add -s <file>` to include file directory while adding')
340 for func in editfuncs:
340 for func in editfuncs:
341 def _wrapper(orig, self, *args):
341 def _wrapper(orig, self, *args):
342 repo = self.repo
342 repo = self.repo
343 if util.safehasattr(repo, 'sparsematch'):
343 if util.safehasattr(repo, 'sparsematch'):
344 dirstate = repo.dirstate
344 dirstate = repo.dirstate
345 sparsematch = repo.sparsematch()
345 sparsematch = repo.sparsematch()
346 for f in args:
346 for f in args:
347 if (f is not None and not sparsematch(f) and
347 if (f is not None and not sparsematch(f) and
348 f not in dirstate):
348 f not in dirstate):
349 raise error.Abort(_("cannot add '%s' - it is outside "
349 raise error.Abort(_("cannot add '%s' - it is outside "
350 "the sparse checkout") % f,
350 "the sparse checkout") % f,
351 hint=hint)
351 hint=hint)
352 return orig(self, *args)
352 return orig(self, *args)
353 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
353 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
354
354
355 def _wraprepo(ui, repo):
355 def _wraprepo(ui, repo):
356 class SparseRepo(repo.__class__):
356 class SparseRepo(repo.__class__):
357 def readsparseconfig(self, raw):
357 def readsparseconfig(self, raw):
358 """Takes a string sparse config and returns the includes,
358 """Takes a string sparse config and returns the includes,
359 excludes, and profiles it specified.
359 excludes, and profiles it specified.
360 """
360 """
361 includes = set()
361 includes = set()
362 excludes = set()
362 excludes = set()
363 current = includes
363 current = includes
364 profiles = []
364 profiles = []
365 for line in raw.split('\n'):
365 for line in raw.split('\n'):
366 line = line.strip()
366 line = line.strip()
367 if not line or line.startswith('#'):
367 if not line or line.startswith('#'):
368 # empty or comment line, skip
368 # empty or comment line, skip
369 continue
369 continue
370 elif line.startswith('%include '):
370 elif line.startswith('%include '):
371 line = line[9:].strip()
371 line = line[9:].strip()
372 if line:
372 if line:
373 profiles.append(line)
373 profiles.append(line)
374 elif line == '[include]':
374 elif line == '[include]':
375 if current != includes:
375 if current != includes:
376 raise error.Abort(_('.hg/sparse cannot have includes ' +
376 raise error.Abort(_('.hg/sparse cannot have includes ' +
377 'after excludes'))
377 'after excludes'))
378 continue
378 continue
379 elif line == '[exclude]':
379 elif line == '[exclude]':
380 current = excludes
380 current = excludes
381 elif line:
381 elif line:
382 if line.strip().startswith('/'):
382 if line.strip().startswith('/'):
383 self.ui.warn(_('warning: sparse profile cannot use' +
383 self.ui.warn(_('warning: sparse profile cannot use' +
384 ' paths starting with /, ignoring %s\n')
384 ' paths starting with /, ignoring %s\n')
385 % line)
385 % line)
386 continue
386 continue
387 current.add(line)
387 current.add(line)
388
388
389 return includes, excludes, profiles
389 return includes, excludes, profiles
390
390
391 def getsparsepatterns(self, rev):
391 def getsparsepatterns(self, rev):
392 """Returns the include/exclude patterns specified by the
392 """Returns the include/exclude patterns specified by the
393 given rev.
393 given rev.
394 """
394 """
395 if not self.vfs.exists('sparse'):
395 if not self.vfs.exists('sparse'):
396 return set(), set(), []
396 return set(), set(), []
397 if rev is None:
397 if rev is None:
398 raise error.Abort(_("cannot parse sparse patterns from " +
398 raise error.Abort(_("cannot parse sparse patterns from " +
399 "working copy"))
399 "working copy"))
400
400
401 raw = self.vfs.read('sparse')
401 raw = self.vfs.read('sparse')
402 includes, excludes, profiles = self.readsparseconfig(raw)
402 includes, excludes, profiles = self.readsparseconfig(raw)
403
403
404 ctx = self[rev]
404 ctx = self[rev]
405 if profiles:
405 if profiles:
406 visited = set()
406 visited = set()
407 while profiles:
407 while profiles:
408 profile = profiles.pop()
408 profile = profiles.pop()
409 if profile in visited:
409 if profile in visited:
410 continue
410 continue
411 visited.add(profile)
411 visited.add(profile)
412
412
413 try:
413 try:
414 raw = self.getrawprofile(profile, rev)
414 raw = self.getrawprofile(profile, rev)
415 except error.ManifestLookupError:
415 except error.ManifestLookupError:
416 msg = (
416 msg = (
417 "warning: sparse profile '%s' not found "
417 "warning: sparse profile '%s' not found "
418 "in rev %s - ignoring it\n" % (profile, ctx))
418 "in rev %s - ignoring it\n" % (profile, ctx))
419 if self.ui.configbool(
419 if self.ui.configbool(
420 'sparse', 'missingwarning', True):
420 'sparse', 'missingwarning', True):
421 self.ui.warn(msg)
421 self.ui.warn(msg)
422 else:
422 else:
423 self.ui.debug(msg)
423 self.ui.debug(msg)
424 continue
424 continue
425 pincludes, pexcludes, subprofs = \
425 pincludes, pexcludes, subprofs = \
426 self.readsparseconfig(raw)
426 self.readsparseconfig(raw)
427 includes.update(pincludes)
427 includes.update(pincludes)
428 excludes.update(pexcludes)
428 excludes.update(pexcludes)
429 for subprofile in subprofs:
429 for subprofile in subprofs:
430 profiles.append(subprofile)
430 profiles.append(subprofile)
431
431
432 profiles = visited
432 profiles = visited
433
433
434 if includes:
434 if includes:
435 includes.add('.hg*')
435 includes.add('.hg*')
436 return includes, excludes, profiles
436 return includes, excludes, profiles
437
437
438 def getrawprofile(self, profile, changeid):
438 def getrawprofile(self, profile, changeid):
439 # TODO add some kind of cache here because this incurs a manifest
439 # TODO add some kind of cache here because this incurs a manifest
440 # resolve and can be slow.
440 # resolve and can be slow.
441 return self.filectx(profile, changeid=changeid).data()
441 return self.filectx(profile, changeid=changeid).data()
442
442
443 def sparsechecksum(self, filepath):
443 def sparsechecksum(self, filepath):
444 fh = open(filepath)
444 fh = open(filepath)
445 return hashlib.sha1(fh.read()).hexdigest()
445 return hashlib.sha1(fh.read()).hexdigest()
446
446
447 def _sparsesignature(self, includetemp=True):
447 def _sparsesignature(self, includetemp=True):
448 """Returns the signature string representing the contents of the
448 """Returns the signature string representing the contents of the
449 current project sparse configuration. This can be used to cache the
449 current project sparse configuration. This can be used to cache the
450 sparse matcher for a given set of revs."""
450 sparse matcher for a given set of revs."""
451 signaturecache = self.signaturecache
451 signaturecache = self.signaturecache
452 signature = signaturecache.get('signature')
452 signature = signaturecache.get('signature')
453 if includetemp:
453 if includetemp:
454 tempsignature = signaturecache.get('tempsignature')
454 tempsignature = signaturecache.get('tempsignature')
455 else:
455 else:
456 tempsignature = 0
456 tempsignature = 0
457
457
458 if signature is None or (includetemp and tempsignature is None):
458 if signature is None or (includetemp and tempsignature is None):
459 signature = 0
459 signature = 0
460 try:
460 try:
461 sparsepath = self.vfs.join('sparse')
461 sparsepath = self.vfs.join('sparse')
462 signature = self.sparsechecksum(sparsepath)
462 signature = self.sparsechecksum(sparsepath)
463 except (OSError, IOError):
463 except (OSError, IOError):
464 pass
464 pass
465 signaturecache['signature'] = signature
465 signaturecache['signature'] = signature
466
466
467 tempsignature = 0
467 tempsignature = 0
468 if includetemp:
468 if includetemp:
469 try:
469 try:
470 tempsparsepath = self.vfs.join('tempsparse')
470 tempsparsepath = self.vfs.join('tempsparse')
471 tempsignature = self.sparsechecksum(tempsparsepath)
471 tempsignature = self.sparsechecksum(tempsparsepath)
472 except (OSError, IOError):
472 except (OSError, IOError):
473 pass
473 pass
474 signaturecache['tempsignature'] = tempsignature
474 signaturecache['tempsignature'] = tempsignature
475 return '%s %s' % (str(signature), str(tempsignature))
475 return '%s %s' % (str(signature), str(tempsignature))
476
476
477 def invalidatecaches(self):
477 def invalidatecaches(self):
478 self.invalidatesignaturecache()
478 self.invalidatesignaturecache()
479 return super(SparseRepo, self).invalidatecaches()
479 return super(SparseRepo, self).invalidatecaches()
480
480
481 def invalidatesignaturecache(self):
481 def invalidatesignaturecache(self):
482 self.signaturecache.clear()
482 self.signaturecache.clear()
483
483
484 def sparsematch(self, *revs, **kwargs):
484 def sparsematch(self, *revs, **kwargs):
485 """Returns the sparse match function for the given revs.
485 """Returns the sparse match function for the given revs.
486
486
487 If multiple revs are specified, the match function is the union
487 If multiple revs are specified, the match function is the union
488 of all the revs.
488 of all the revs.
489
489
490 `includetemp` is used to indicate if the temporarily included file
490 `includetemp` is used to indicate if the temporarily included file
491 should be part of the matcher.
491 should be part of the matcher.
492 """
492 """
493 if not revs or revs == (None,):
493 if not revs or revs == (None,):
494 revs = [self.changelog.rev(node) for node in
494 revs = [self.changelog.rev(node) for node in
495 self.dirstate.parents() if node != nullid]
495 self.dirstate.parents() if node != nullid]
496
496
497 includetemp = kwargs.get('includetemp', True)
497 includetemp = kwargs.get('includetemp', True)
498 signature = self._sparsesignature(includetemp=includetemp)
498 signature = self._sparsesignature(includetemp=includetemp)
499
499
500 key = '%s %s' % (str(signature), ' '.join([str(r) for r in revs]))
500 key = '%s %s' % (str(signature), ' '.join([str(r) for r in revs]))
501
501
502 result = self.sparsecache.get(key, None)
502 result = self.sparsecache.get(key, None)
503 if result:
503 if result:
504 return result
504 return result
505
505
506 matchers = []
506 matchers = []
507 for rev in revs:
507 for rev in revs:
508 try:
508 try:
509 includes, excludes, profiles = self.getsparsepatterns(rev)
509 includes, excludes, profiles = self.getsparsepatterns(rev)
510
510
511 if includes or excludes:
511 if includes or excludes:
512 # Explicitly include subdirectories of includes so
512 # Explicitly include subdirectories of includes so
513 # status will walk them down to the actual include.
513 # status will walk them down to the actual include.
514 subdirs = set()
514 subdirs = set()
515 for include in includes:
515 for include in includes:
516 dirname = os.path.dirname(include)
516 dirname = os.path.dirname(include)
517 # basename is used to avoid issues with absolute
517 # basename is used to avoid issues with absolute
518 # paths (which on Windows can include the drive).
518 # paths (which on Windows can include the drive).
519 while os.path.basename(dirname):
519 while os.path.basename(dirname):
520 subdirs.add(dirname)
520 subdirs.add(dirname)
521 dirname = os.path.dirname(dirname)
521 dirname = os.path.dirname(dirname)
522
522
523 matcher = matchmod.match(self.root, '', [],
523 matcher = matchmod.match(self.root, '', [],
524 include=includes, exclude=excludes,
524 include=includes, exclude=excludes,
525 default='relpath')
525 default='relpath')
526 if subdirs:
526 if subdirs:
527 matcher = forceincludematcher(matcher, subdirs)
527 matcher = forceincludematcher(matcher, subdirs)
528 matchers.append(matcher)
528 matchers.append(matcher)
529 except IOError:
529 except IOError:
530 pass
530 pass
531
531
532 result = None
532 result = None
533 if not matchers:
533 if not matchers:
534 result = matchmod.always(self.root, '')
534 result = matchmod.always(self.root, '')
535 elif len(matchers) == 1:
535 elif len(matchers) == 1:
536 result = matchers[0]
536 result = matchers[0]
537 else:
537 else:
538 result = unionmatcher(matchers)
538 result = unionmatcher(matchers)
539
539
540 if kwargs.get('includetemp', True):
540 if kwargs.get('includetemp', True):
541 tempincludes = self.gettemporaryincludes()
541 tempincludes = self.gettemporaryincludes()
542 result = forceincludematcher(result, tempincludes)
542 result = forceincludematcher(result, tempincludes)
543
543
544 self.sparsecache[key] = result
544 self.sparsecache[key] = result
545
545
546 return result
546 return result
547
547
548 def getactiveprofiles(self):
548 def getactiveprofiles(self):
549 revs = [self.changelog.rev(node) for node in
549 revs = [self.changelog.rev(node) for node in
550 self.dirstate.parents() if node != nullid]
550 self.dirstate.parents() if node != nullid]
551
551
552 activeprofiles = set()
552 activeprofiles = set()
553 for rev in revs:
553 for rev in revs:
554 _, _, profiles = self.getsparsepatterns(rev)
554 _, _, profiles = self.getsparsepatterns(rev)
555 activeprofiles.update(profiles)
555 activeprofiles.update(profiles)
556
556
557 return activeprofiles
557 return activeprofiles
558
558
559 def writesparseconfig(self, include, exclude, profiles):
559 def writesparseconfig(self, include, exclude, profiles):
560 raw = '%s[include]\n%s\n[exclude]\n%s\n' % (
560 raw = '%s[include]\n%s\n[exclude]\n%s\n' % (
561 ''.join(['%%include %s\n' % p for p in sorted(profiles)]),
561 ''.join(['%%include %s\n' % p for p in sorted(profiles)]),
562 '\n'.join(sorted(include)),
562 '\n'.join(sorted(include)),
563 '\n'.join(sorted(exclude)))
563 '\n'.join(sorted(exclude)))
564 self.vfs.write("sparse", raw)
564 self.vfs.write("sparse", raw)
565 self.invalidatesignaturecache()
565 self.invalidatesignaturecache()
566
566
567 def addtemporaryincludes(self, files):
567 def addtemporaryincludes(self, files):
568 includes = self.gettemporaryincludes()
568 includes = self.gettemporaryincludes()
569 for file in files:
569 for file in files:
570 includes.add(file)
570 includes.add(file)
571 self._writetemporaryincludes(includes)
571 self._writetemporaryincludes(includes)
572
572
573 def gettemporaryincludes(self):
573 def gettemporaryincludes(self):
574 existingtemp = set()
574 existingtemp = set()
575 if self.vfs.exists('tempsparse'):
575 if self.vfs.exists('tempsparse'):
576 raw = self.vfs.read('tempsparse')
576 raw = self.vfs.read('tempsparse')
577 existingtemp.update(raw.split('\n'))
577 existingtemp.update(raw.split('\n'))
578 return existingtemp
578 return existingtemp
579
579
580 def _writetemporaryincludes(self, includes):
580 def _writetemporaryincludes(self, includes):
581 raw = '\n'.join(sorted(includes))
581 raw = '\n'.join(sorted(includes))
582 self.vfs.write('tempsparse', raw)
582 self.vfs.write('tempsparse', raw)
583 self.invalidatesignaturecache()
583 self.invalidatesignaturecache()
584
584
585 def prunetemporaryincludes(self):
585 def prunetemporaryincludes(self):
586 if repo.vfs.exists('tempsparse'):
586 if repo.vfs.exists('tempsparse'):
587 origstatus = self.status()
587 origstatus = self.status()
588 modified, added, removed, deleted, a, b, c = origstatus
588 modified, added, removed, deleted, a, b, c = origstatus
589 if modified or added or removed or deleted:
589 if modified or added or removed or deleted:
590 # Still have pending changes. Don't bother trying to prune.
590 # Still have pending changes. Don't bother trying to prune.
591 return
591 return
592
592
593 sparsematch = self.sparsematch(includetemp=False)
593 sparsematch = self.sparsematch(includetemp=False)
594 dirstate = self.dirstate
594 dirstate = self.dirstate
595 actions = []
595 actions = []
596 dropped = []
596 dropped = []
597 tempincludes = self.gettemporaryincludes()
597 tempincludes = self.gettemporaryincludes()
598 for file in tempincludes:
598 for file in tempincludes:
599 if file in dirstate and not sparsematch(file):
599 if file in dirstate and not sparsematch(file):
600 message = 'dropping temporarily included sparse files'
600 message = 'dropping temporarily included sparse files'
601 actions.append((file, None, message))
601 actions.append((file, None, message))
602 dropped.append(file)
602 dropped.append(file)
603
603
604 typeactions = collections.defaultdict(list)
604 typeactions = collections.defaultdict(list)
605 typeactions['r'] = actions
605 typeactions['r'] = actions
606 mergemod.applyupdates(self, typeactions, self[None], self['.'],
606 mergemod.applyupdates(self, typeactions, self[None], self['.'],
607 False)
607 False)
608
608
609 # Fix dirstate
609 # Fix dirstate
610 for file in dropped:
610 for file in dropped:
611 dirstate.drop(file)
611 dirstate.drop(file)
612
612
613 self.vfs.unlink('tempsparse')
613 self.vfs.unlink('tempsparse')
614 self.invalidatesignaturecache()
614 self.invalidatesignaturecache()
615 msg = _("cleaned up %d temporarily added file(s) from the "
615 msg = _("cleaned up %d temporarily added file(s) from the "
616 "sparse checkout\n")
616 "sparse checkout\n")
617 ui.status(msg % len(tempincludes))
617 ui.status(msg % len(tempincludes))
618
618
619 if 'dirstate' in repo._filecache:
619 if 'dirstate' in repo._filecache:
620 repo.dirstate.repo = repo
620 repo.dirstate.repo = repo
621 repo.sparsecache = {}
621 repo.sparsecache = {}
622 repo.signaturecache = {}
622 repo.signaturecache = {}
623 repo.__class__ = SparseRepo
623 repo.__class__ = SparseRepo
624
624
625 @command('^sparse', [
625 @command('^debugsparse', [
626 ('I', 'include', False, _('include files in the sparse checkout')),
626 ('I', 'include', False, _('include files in the sparse checkout')),
627 ('X', 'exclude', False, _('exclude files in the sparse checkout')),
627 ('X', 'exclude', False, _('exclude files in the sparse checkout')),
628 ('d', 'delete', False, _('delete an include/exclude rule')),
628 ('d', 'delete', False, _('delete an include/exclude rule')),
629 ('f', 'force', False, _('allow changing rules even with pending changes')),
629 ('f', 'force', False, _('allow changing rules even with pending changes')),
630 ('', 'enable-profile', False, _('enables the specified profile')),
630 ('', 'enable-profile', False, _('enables the specified profile')),
631 ('', 'disable-profile', False, _('disables the specified profile')),
631 ('', 'disable-profile', False, _('disables the specified profile')),
632 ('', 'import-rules', False, _('imports rules from a file')),
632 ('', 'import-rules', False, _('imports rules from a file')),
633 ('', 'clear-rules', False, _('clears local include/exclude rules')),
633 ('', 'clear-rules', False, _('clears local include/exclude rules')),
634 ('', 'refresh', False, _('updates the working after sparseness changes')),
634 ('', 'refresh', False, _('updates the working after sparseness changes')),
635 ('', 'reset', False, _('makes the repo full again')),
635 ('', 'reset', False, _('makes the repo full again')),
636 ] + commands.templateopts,
636 ] + commands.templateopts,
637 _('[--OPTION] PATTERN...'))
637 _('[--OPTION] PATTERN...'))
638 def sparse(ui, repo, *pats, **opts):
638 def debugsparse(ui, repo, *pats, **opts):
639 """make the current checkout sparse, or edit the existing checkout
639 """make the current checkout sparse, or edit the existing checkout
640
640
641 The sparse command is used to make the current checkout sparse.
641 The sparse command is used to make the current checkout sparse.
642 This means files that don't meet the sparse condition will not be
642 This means files that don't meet the sparse condition will not be
643 written to disk, or show up in any working copy operations. It does
643 written to disk, or show up in any working copy operations. It does
644 not affect files in history in any way.
644 not affect files in history in any way.
645
645
646 Passing no arguments prints the currently applied sparse rules.
646 Passing no arguments prints the currently applied sparse rules.
647
647
648 --include and --exclude are used to add and remove files from the sparse
648 --include and --exclude are used to add and remove files from the sparse
649 checkout. The effects of adding an include or exclude rule are applied
649 checkout. The effects of adding an include or exclude rule are applied
650 immediately. If applying the new rule would cause a file with pending
650 immediately. If applying the new rule would cause a file with pending
651 changes to be added or removed, the command will fail. Pass --force to
651 changes to be added or removed, the command will fail. Pass --force to
652 force a rule change even with pending changes (the changes on disk will
652 force a rule change even with pending changes (the changes on disk will
653 be preserved).
653 be preserved).
654
654
655 --delete removes an existing include/exclude rule. The effects are
655 --delete removes an existing include/exclude rule. The effects are
656 immediate.
656 immediate.
657
657
658 --refresh refreshes the files on disk based on the sparse rules. This is
658 --refresh refreshes the files on disk based on the sparse rules. This is
659 only necessary if .hg/sparse was changed by hand.
659 only necessary if .hg/sparse was changed by hand.
660
660
661 --enable-profile and --disable-profile accept a path to a .hgsparse file.
661 --enable-profile and --disable-profile accept a path to a .hgsparse file.
662 This allows defining sparse checkouts and tracking them inside the
662 This allows defining sparse checkouts and tracking them inside the
663 repository. This is useful for defining commonly used sparse checkouts for
663 repository. This is useful for defining commonly used sparse checkouts for
664 many people to use. As the profile definition changes over time, the sparse
664 many people to use. As the profile definition changes over time, the sparse
665 checkout will automatically be updated appropriately, depending on which
665 checkout will automatically be updated appropriately, depending on which
666 changeset is checked out. Changes to .hgsparse are not applied until they
666 changeset is checked out. Changes to .hgsparse are not applied until they
667 have been committed.
667 have been committed.
668
668
669 --import-rules accepts a path to a file containing rules in the .hgsparse
669 --import-rules accepts a path to a file containing rules in the .hgsparse
670 format, allowing you to add --include, --exclude and --enable-profile rules
670 format, allowing you to add --include, --exclude and --enable-profile rules
671 in bulk. Like the --include, --exclude and --enable-profile switches, the
671 in bulk. Like the --include, --exclude and --enable-profile switches, the
672 changes are applied immediately.
672 changes are applied immediately.
673
673
674 --clear-rules removes all local include and exclude rules, while leaving
674 --clear-rules removes all local include and exclude rules, while leaving
675 any enabled profiles in place.
675 any enabled profiles in place.
676
676
677 Returns 0 if editing the sparse checkout succeeds.
677 Returns 0 if editing the sparse checkout succeeds.
678 """
678 """
679 include = opts.get('include')
679 include = opts.get('include')
680 exclude = opts.get('exclude')
680 exclude = opts.get('exclude')
681 force = opts.get('force')
681 force = opts.get('force')
682 enableprofile = opts.get('enable_profile')
682 enableprofile = opts.get('enable_profile')
683 disableprofile = opts.get('disable_profile')
683 disableprofile = opts.get('disable_profile')
684 importrules = opts.get('import_rules')
684 importrules = opts.get('import_rules')
685 clearrules = opts.get('clear_rules')
685 clearrules = opts.get('clear_rules')
686 delete = opts.get('delete')
686 delete = opts.get('delete')
687 refresh = opts.get('refresh')
687 refresh = opts.get('refresh')
688 reset = opts.get('reset')
688 reset = opts.get('reset')
689 count = sum([include, exclude, enableprofile, disableprofile, delete,
689 count = sum([include, exclude, enableprofile, disableprofile, delete,
690 importrules, refresh, clearrules, reset])
690 importrules, refresh, clearrules, reset])
691 if count > 1:
691 if count > 1:
692 raise error.Abort(_("too many flags specified"))
692 raise error.Abort(_("too many flags specified"))
693
693
694 if count == 0:
694 if count == 0:
695 if repo.vfs.exists('sparse'):
695 if repo.vfs.exists('sparse'):
696 ui.status(repo.vfs.read("sparse") + "\n")
696 ui.status(repo.vfs.read("sparse") + "\n")
697 temporaryincludes = repo.gettemporaryincludes()
697 temporaryincludes = repo.gettemporaryincludes()
698 if temporaryincludes:
698 if temporaryincludes:
699 ui.status(_("Temporarily Included Files (for merge/rebase):\n"))
699 ui.status(_("Temporarily Included Files (for merge/rebase):\n"))
700 ui.status(("\n".join(temporaryincludes) + "\n"))
700 ui.status(("\n".join(temporaryincludes) + "\n"))
701 else:
701 else:
702 ui.status(_('repo is not sparse\n'))
702 ui.status(_('repo is not sparse\n'))
703 return
703 return
704
704
705 if include or exclude or delete or reset or enableprofile or disableprofile:
705 if include or exclude or delete or reset or enableprofile or disableprofile:
706 _config(ui, repo, pats, opts, include=include, exclude=exclude,
706 _config(ui, repo, pats, opts, include=include, exclude=exclude,
707 reset=reset, delete=delete, enableprofile=enableprofile,
707 reset=reset, delete=delete, enableprofile=enableprofile,
708 disableprofile=disableprofile, force=force)
708 disableprofile=disableprofile, force=force)
709
709
710 if importrules:
710 if importrules:
711 _import(ui, repo, pats, opts, force=force)
711 _import(ui, repo, pats, opts, force=force)
712
712
713 if clearrules:
713 if clearrules:
714 _clear(ui, repo, pats, force=force)
714 _clear(ui, repo, pats, force=force)
715
715
716 if refresh:
716 if refresh:
717 try:
717 try:
718 wlock = repo.wlock()
718 wlock = repo.wlock()
719 fcounts = map(
719 fcounts = map(
720 len,
720 len,
721 _refresh(ui, repo, repo.status(), repo.sparsematch(), force))
721 _refresh(ui, repo, repo.status(), repo.sparsematch(), force))
722 _verbose_output(ui, opts, 0, 0, 0, *fcounts)
722 _verbose_output(ui, opts, 0, 0, 0, *fcounts)
723 finally:
723 finally:
724 wlock.release()
724 wlock.release()
725
725
726 def _config(ui, repo, pats, opts, include=False, exclude=False, reset=False,
726 def _config(ui, repo, pats, opts, include=False, exclude=False, reset=False,
727 delete=False, enableprofile=False, disableprofile=False,
727 delete=False, enableprofile=False, disableprofile=False,
728 force=False):
728 force=False):
729 """
729 """
730 Perform a sparse config update. Only one of the kwargs may be specified.
730 Perform a sparse config update. Only one of the kwargs may be specified.
731 """
731 """
732 wlock = repo.wlock()
732 wlock = repo.wlock()
733 try:
733 try:
734 oldsparsematch = repo.sparsematch()
734 oldsparsematch = repo.sparsematch()
735
735
736 if repo.vfs.exists('sparse'):
736 if repo.vfs.exists('sparse'):
737 raw = repo.vfs.read('sparse')
737 raw = repo.vfs.read('sparse')
738 oldinclude, oldexclude, oldprofiles = map(
738 oldinclude, oldexclude, oldprofiles = map(
739 set, repo.readsparseconfig(raw))
739 set, repo.readsparseconfig(raw))
740 else:
740 else:
741 oldinclude = set()
741 oldinclude = set()
742 oldexclude = set()
742 oldexclude = set()
743 oldprofiles = set()
743 oldprofiles = set()
744
744
745 try:
745 try:
746 if reset:
746 if reset:
747 newinclude = set()
747 newinclude = set()
748 newexclude = set()
748 newexclude = set()
749 newprofiles = set()
749 newprofiles = set()
750 else:
750 else:
751 newinclude = set(oldinclude)
751 newinclude = set(oldinclude)
752 newexclude = set(oldexclude)
752 newexclude = set(oldexclude)
753 newprofiles = set(oldprofiles)
753 newprofiles = set(oldprofiles)
754
754
755 oldstatus = repo.status()
755 oldstatus = repo.status()
756
756
757 if any(pat.startswith('/') for pat in pats):
757 if any(pat.startswith('/') for pat in pats):
758 ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
758 ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
759 % ([pat for pat in pats if pat.startswith('/')]))
759 % ([pat for pat in pats if pat.startswith('/')]))
760 elif include:
760 elif include:
761 newinclude.update(pats)
761 newinclude.update(pats)
762 elif exclude:
762 elif exclude:
763 newexclude.update(pats)
763 newexclude.update(pats)
764 elif enableprofile:
764 elif enableprofile:
765 newprofiles.update(pats)
765 newprofiles.update(pats)
766 elif disableprofile:
766 elif disableprofile:
767 newprofiles.difference_update(pats)
767 newprofiles.difference_update(pats)
768 elif delete:
768 elif delete:
769 newinclude.difference_update(pats)
769 newinclude.difference_update(pats)
770 newexclude.difference_update(pats)
770 newexclude.difference_update(pats)
771
771
772 repo.writesparseconfig(newinclude, newexclude, newprofiles)
772 repo.writesparseconfig(newinclude, newexclude, newprofiles)
773 fcounts = map(
773 fcounts = map(
774 len, _refresh(ui, repo, oldstatus, oldsparsematch, force))
774 len, _refresh(ui, repo, oldstatus, oldsparsematch, force))
775
775
776 profilecount = (len(newprofiles - oldprofiles) -
776 profilecount = (len(newprofiles - oldprofiles) -
777 len(oldprofiles - newprofiles))
777 len(oldprofiles - newprofiles))
778 includecount = (len(newinclude - oldinclude) -
778 includecount = (len(newinclude - oldinclude) -
779 len(oldinclude - newinclude))
779 len(oldinclude - newinclude))
780 excludecount = (len(newexclude - oldexclude) -
780 excludecount = (len(newexclude - oldexclude) -
781 len(oldexclude - newexclude))
781 len(oldexclude - newexclude))
782 _verbose_output(
782 _verbose_output(
783 ui, opts, profilecount, includecount, excludecount, *fcounts)
783 ui, opts, profilecount, includecount, excludecount, *fcounts)
784 except Exception:
784 except Exception:
785 repo.writesparseconfig(oldinclude, oldexclude, oldprofiles)
785 repo.writesparseconfig(oldinclude, oldexclude, oldprofiles)
786 raise
786 raise
787 finally:
787 finally:
788 wlock.release()
788 wlock.release()
789
789
790 def _import(ui, repo, files, opts, force=False):
790 def _import(ui, repo, files, opts, force=False):
791 with repo.wlock():
791 with repo.wlock():
792 # load union of current active profile
792 # load union of current active profile
793 revs = [repo.changelog.rev(node) for node in
793 revs = [repo.changelog.rev(node) for node in
794 repo.dirstate.parents() if node != nullid]
794 repo.dirstate.parents() if node != nullid]
795
795
796 # read current configuration
796 # read current configuration
797 raw = ''
797 raw = ''
798 if repo.vfs.exists('sparse'):
798 if repo.vfs.exists('sparse'):
799 raw = repo.vfs.read('sparse')
799 raw = repo.vfs.read('sparse')
800 oincludes, oexcludes, oprofiles = repo.readsparseconfig(raw)
800 oincludes, oexcludes, oprofiles = repo.readsparseconfig(raw)
801 includes, excludes, profiles = map(
801 includes, excludes, profiles = map(
802 set, (oincludes, oexcludes, oprofiles))
802 set, (oincludes, oexcludes, oprofiles))
803
803
804 # all active rules
804 # all active rules
805 aincludes, aexcludes, aprofiles = set(), set(), set()
805 aincludes, aexcludes, aprofiles = set(), set(), set()
806 for rev in revs:
806 for rev in revs:
807 rincludes, rexcludes, rprofiles = repo.getsparsepatterns(rev)
807 rincludes, rexcludes, rprofiles = repo.getsparsepatterns(rev)
808 aincludes.update(rincludes)
808 aincludes.update(rincludes)
809 aexcludes.update(rexcludes)
809 aexcludes.update(rexcludes)
810 aprofiles.update(rprofiles)
810 aprofiles.update(rprofiles)
811
811
812 # import rules on top; only take in rules that are not yet
812 # import rules on top; only take in rules that are not yet
813 # part of the active rules.
813 # part of the active rules.
814 changed = False
814 changed = False
815 for file in files:
815 for file in files:
816 with util.posixfile(util.expandpath(file)) as importfile:
816 with util.posixfile(util.expandpath(file)) as importfile:
817 iincludes, iexcludes, iprofiles = repo.readsparseconfig(
817 iincludes, iexcludes, iprofiles = repo.readsparseconfig(
818 importfile.read())
818 importfile.read())
819 oldsize = len(includes) + len(excludes) + len(profiles)
819 oldsize = len(includes) + len(excludes) + len(profiles)
820 includes.update(iincludes - aincludes)
820 includes.update(iincludes - aincludes)
821 excludes.update(iexcludes - aexcludes)
821 excludes.update(iexcludes - aexcludes)
822 profiles.update(set(iprofiles) - aprofiles)
822 profiles.update(set(iprofiles) - aprofiles)
823 if len(includes) + len(excludes) + len(profiles) > oldsize:
823 if len(includes) + len(excludes) + len(profiles) > oldsize:
824 changed = True
824 changed = True
825
825
826 profilecount = includecount = excludecount = 0
826 profilecount = includecount = excludecount = 0
827 fcounts = (0, 0, 0)
827 fcounts = (0, 0, 0)
828
828
829 if changed:
829 if changed:
830 profilecount = len(profiles - aprofiles)
830 profilecount = len(profiles - aprofiles)
831 includecount = len(includes - aincludes)
831 includecount = len(includes - aincludes)
832 excludecount = len(excludes - aexcludes)
832 excludecount = len(excludes - aexcludes)
833
833
834 oldstatus = repo.status()
834 oldstatus = repo.status()
835 oldsparsematch = repo.sparsematch()
835 oldsparsematch = repo.sparsematch()
836 repo.writesparseconfig(includes, excludes, profiles)
836 repo.writesparseconfig(includes, excludes, profiles)
837
837
838 try:
838 try:
839 fcounts = map(
839 fcounts = map(
840 len, _refresh(ui, repo, oldstatus, oldsparsematch, force))
840 len, _refresh(ui, repo, oldstatus, oldsparsematch, force))
841 except Exception:
841 except Exception:
842 repo.writesparseconfig(oincludes, oexcludes, oprofiles)
842 repo.writesparseconfig(oincludes, oexcludes, oprofiles)
843 raise
843 raise
844
844
845 _verbose_output(ui, opts, profilecount, includecount, excludecount,
845 _verbose_output(ui, opts, profilecount, includecount, excludecount,
846 *fcounts)
846 *fcounts)
847
847
848 def _clear(ui, repo, files, force=False):
848 def _clear(ui, repo, files, force=False):
849 with repo.wlock():
849 with repo.wlock():
850 raw = ''
850 raw = ''
851 if repo.vfs.exists('sparse'):
851 if repo.vfs.exists('sparse'):
852 raw = repo.vfs.read('sparse')
852 raw = repo.vfs.read('sparse')
853 includes, excludes, profiles = repo.readsparseconfig(raw)
853 includes, excludes, profiles = repo.readsparseconfig(raw)
854
854
855 if includes or excludes:
855 if includes or excludes:
856 oldstatus = repo.status()
856 oldstatus = repo.status()
857 oldsparsematch = repo.sparsematch()
857 oldsparsematch = repo.sparsematch()
858 repo.writesparseconfig(set(), set(), profiles)
858 repo.writesparseconfig(set(), set(), profiles)
859 _refresh(ui, repo, oldstatus, oldsparsematch, force)
859 _refresh(ui, repo, oldstatus, oldsparsematch, force)
860
860
861 def _refresh(ui, repo, origstatus, origsparsematch, force):
861 def _refresh(ui, repo, origstatus, origsparsematch, force):
862 """Refreshes which files are on disk by comparing the old status and
862 """Refreshes which files are on disk by comparing the old status and
863 sparsematch with the new sparsematch.
863 sparsematch with the new sparsematch.
864
864
865 Will raise an exception if a file with pending changes is being excluded
865 Will raise an exception if a file with pending changes is being excluded
866 or included (unless force=True).
866 or included (unless force=True).
867 """
867 """
868 modified, added, removed, deleted, unknown, ignored, clean = origstatus
868 modified, added, removed, deleted, unknown, ignored, clean = origstatus
869
869
870 # Verify there are no pending changes
870 # Verify there are no pending changes
871 pending = set()
871 pending = set()
872 pending.update(modified)
872 pending.update(modified)
873 pending.update(added)
873 pending.update(added)
874 pending.update(removed)
874 pending.update(removed)
875 sparsematch = repo.sparsematch()
875 sparsematch = repo.sparsematch()
876 abort = False
876 abort = False
877 for file in pending:
877 for file in pending:
878 if not sparsematch(file):
878 if not sparsematch(file):
879 ui.warn(_("pending changes to '%s'\n") % file)
879 ui.warn(_("pending changes to '%s'\n") % file)
880 abort = not force
880 abort = not force
881 if abort:
881 if abort:
882 raise error.Abort(_("could not update sparseness due to " +
882 raise error.Abort(_("could not update sparseness due to " +
883 "pending changes"))
883 "pending changes"))
884
884
885 # Calculate actions
885 # Calculate actions
886 dirstate = repo.dirstate
886 dirstate = repo.dirstate
887 ctx = repo['.']
887 ctx = repo['.']
888 added = []
888 added = []
889 lookup = []
889 lookup = []
890 dropped = []
890 dropped = []
891 mf = ctx.manifest()
891 mf = ctx.manifest()
892 files = set(mf)
892 files = set(mf)
893
893
894 actions = {}
894 actions = {}
895
895
896 for file in files:
896 for file in files:
897 old = origsparsematch(file)
897 old = origsparsematch(file)
898 new = sparsematch(file)
898 new = sparsematch(file)
899 # Add files that are newly included, or that don't exist in
899 # Add files that are newly included, or that don't exist in
900 # the dirstate yet.
900 # the dirstate yet.
901 if (new and not old) or (old and new and not file in dirstate):
901 if (new and not old) or (old and new and not file in dirstate):
902 fl = mf.flags(file)
902 fl = mf.flags(file)
903 if repo.wvfs.exists(file):
903 if repo.wvfs.exists(file):
904 actions[file] = ('e', (fl,), '')
904 actions[file] = ('e', (fl,), '')
905 lookup.append(file)
905 lookup.append(file)
906 else:
906 else:
907 actions[file] = ('g', (fl, False), '')
907 actions[file] = ('g', (fl, False), '')
908 added.append(file)
908 added.append(file)
909 # Drop files that are newly excluded, or that still exist in
909 # Drop files that are newly excluded, or that still exist in
910 # the dirstate.
910 # the dirstate.
911 elif (old and not new) or (not old and not new and file in dirstate):
911 elif (old and not new) or (not old and not new and file in dirstate):
912 dropped.append(file)
912 dropped.append(file)
913 if file not in pending:
913 if file not in pending:
914 actions[file] = ('r', [], '')
914 actions[file] = ('r', [], '')
915
915
916 # Verify there are no pending changes in newly included files
916 # Verify there are no pending changes in newly included files
917 abort = False
917 abort = False
918 for file in lookup:
918 for file in lookup:
919 ui.warn(_("pending changes to '%s'\n") % file)
919 ui.warn(_("pending changes to '%s'\n") % file)
920 abort = not force
920 abort = not force
921 if abort:
921 if abort:
922 raise error.Abort(_("cannot change sparseness due to " +
922 raise error.Abort(_("cannot change sparseness due to " +
923 "pending changes (delete the files or use --force " +
923 "pending changes (delete the files or use --force " +
924 "to bring them back dirty)"))
924 "to bring them back dirty)"))
925
925
926 # Check for files that were only in the dirstate.
926 # Check for files that were only in the dirstate.
927 for file, state in dirstate.iteritems():
927 for file, state in dirstate.iteritems():
928 if not file in files:
928 if not file in files:
929 old = origsparsematch(file)
929 old = origsparsematch(file)
930 new = sparsematch(file)
930 new = sparsematch(file)
931 if old and not new:
931 if old and not new:
932 dropped.append(file)
932 dropped.append(file)
933
933
934 # Apply changes to disk
934 # Apply changes to disk
935 typeactions = dict((m, []) for m in 'a f g am cd dc r dm dg m e k'.split())
935 typeactions = dict((m, []) for m in 'a f g am cd dc r dm dg m e k'.split())
936 for f, (m, args, msg) in actions.iteritems():
936 for f, (m, args, msg) in actions.iteritems():
937 if m not in typeactions:
937 if m not in typeactions:
938 typeactions[m] = []
938 typeactions[m] = []
939 typeactions[m].append((f, args, msg))
939 typeactions[m].append((f, args, msg))
940 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False)
940 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False)
941
941
942 # Fix dirstate
942 # Fix dirstate
943 for file in added:
943 for file in added:
944 dirstate.normal(file)
944 dirstate.normal(file)
945
945
946 for file in dropped:
946 for file in dropped:
947 dirstate.drop(file)
947 dirstate.drop(file)
948
948
949 for file in lookup:
949 for file in lookup:
950 # File exists on disk, and we're bringing it back in an unknown state.
950 # File exists on disk, and we're bringing it back in an unknown state.
951 dirstate.normallookup(file)
951 dirstate.normallookup(file)
952
952
953 return added, dropped, lookup
953 return added, dropped, lookup
954
954
955 def _verbose_output(ui, opts, profilecount, includecount, excludecount, added,
955 def _verbose_output(ui, opts, profilecount, includecount, excludecount, added,
956 dropped, lookup):
956 dropped, lookup):
957 """Produce --verbose and templatable output
957 """Produce --verbose and templatable output
958
958
959 This specifically enables -Tjson, providing machine-readable stats on how
959 This specifically enables -Tjson, providing machine-readable stats on how
960 the sparse profile changed.
960 the sparse profile changed.
961
961
962 """
962 """
963 with ui.formatter('sparse', opts) as fm:
963 with ui.formatter('sparse', opts) as fm:
964 fm.startitem()
964 fm.startitem()
965 fm.condwrite(ui.verbose, 'profiles_added', 'Profile # change: %d\n',
965 fm.condwrite(ui.verbose, 'profiles_added', 'Profile # change: %d\n',
966 profilecount)
966 profilecount)
967 fm.condwrite(ui.verbose, 'include_rules_added',
967 fm.condwrite(ui.verbose, 'include_rules_added',
968 'Include rule # change: %d\n', includecount)
968 'Include rule # change: %d\n', includecount)
969 fm.condwrite(ui.verbose, 'exclude_rules_added',
969 fm.condwrite(ui.verbose, 'exclude_rules_added',
970 'Exclude rule # change: %d\n', excludecount)
970 'Exclude rule # change: %d\n', excludecount)
971 # In 'plain' verbose mode, mergemod.applyupdates already outputs what
971 # In 'plain' verbose mode, mergemod.applyupdates already outputs what
972 # files are added or removed outside of the templating formatter
972 # files are added or removed outside of the templating formatter
973 # framework. No point in repeating ourselves in that case.
973 # framework. No point in repeating ourselves in that case.
974 if not fm.isplain():
974 if not fm.isplain():
975 fm.condwrite(ui.verbose, 'files_added', 'Files added: %d\n',
975 fm.condwrite(ui.verbose, 'files_added', 'Files added: %d\n',
976 added)
976 added)
977 fm.condwrite(ui.verbose, 'files_dropped', 'Files dropped: %d\n',
977 fm.condwrite(ui.verbose, 'files_dropped', 'Files dropped: %d\n',
978 dropped)
978 dropped)
979 fm.condwrite(ui.verbose, 'files_conflicting',
979 fm.condwrite(ui.verbose, 'files_conflicting',
980 'Files conflicting: %d\n', lookup)
980 'Files conflicting: %d\n', lookup)
981
981
982 class forceincludematcher(object):
982 class forceincludematcher(object):
983 """A matcher that returns true for any of the forced includes before testing
983 """A matcher that returns true for any of the forced includes before testing
984 against the actual matcher."""
984 against the actual matcher."""
985 def __init__(self, matcher, includes):
985 def __init__(self, matcher, includes):
986 self._matcher = matcher
986 self._matcher = matcher
987 self._includes = includes
987 self._includes = includes
988
988
989 def __call__(self, value):
989 def __call__(self, value):
990 return value in self._includes or self._matcher(value)
990 return value in self._includes or self._matcher(value)
991
991
992 def always(self):
992 def always(self):
993 return False
993 return False
994
994
995 def files(self):
995 def files(self):
996 return []
996 return []
997
997
998 def isexact(self):
998 def isexact(self):
999 return False
999 return False
1000
1000
1001 def anypats(self):
1001 def anypats(self):
1002 return True
1002 return True
1003
1003
1004 def prefix(self):
1004 def prefix(self):
1005 return False
1005 return False
1006
1006
1007 def hash(self):
1007 def hash(self):
1008 sha1 = hashlib.sha1()
1008 sha1 = hashlib.sha1()
1009 sha1.update(_hashmatcher(self._matcher))
1009 sha1.update(_hashmatcher(self._matcher))
1010 for include in sorted(self._includes):
1010 for include in sorted(self._includes):
1011 sha1.update(include + '\0')
1011 sha1.update(include + '\0')
1012 return sha1.hexdigest()
1012 return sha1.hexdigest()
1013
1013
1014 class unionmatcher(object):
1014 class unionmatcher(object):
1015 """A matcher that is the union of several matchers."""
1015 """A matcher that is the union of several matchers."""
1016 def __init__(self, matchers):
1016 def __init__(self, matchers):
1017 self._matchers = matchers
1017 self._matchers = matchers
1018
1018
1019 def __call__(self, value):
1019 def __call__(self, value):
1020 for match in self._matchers:
1020 for match in self._matchers:
1021 if match(value):
1021 if match(value):
1022 return True
1022 return True
1023 return False
1023 return False
1024
1024
1025 def always(self):
1025 def always(self):
1026 return False
1026 return False
1027
1027
1028 def files(self):
1028 def files(self):
1029 return []
1029 return []
1030
1030
1031 def isexact(self):
1031 def isexact(self):
1032 return False
1032 return False
1033
1033
1034 def anypats(self):
1034 def anypats(self):
1035 return True
1035 return True
1036
1036
1037 def prefix(self):
1037 def prefix(self):
1038 return False
1038 return False
1039
1039
1040 def hash(self):
1040 def hash(self):
1041 sha1 = hashlib.sha1()
1041 sha1 = hashlib.sha1()
1042 for m in self._matchers:
1042 for m in self._matchers:
1043 sha1.update(_hashmatcher(m))
1043 sha1.update(_hashmatcher(m))
1044 return sha1.hexdigest()
1044 return sha1.hexdigest()
1045
1045
1046 class negatematcher(object):
1046 class negatematcher(object):
1047 def __init__(self, matcher):
1047 def __init__(self, matcher):
1048 self._matcher = matcher
1048 self._matcher = matcher
1049
1049
1050 def __call__(self, value):
1050 def __call__(self, value):
1051 return not self._matcher(value)
1051 return not self._matcher(value)
1052
1052
1053 def always(self):
1053 def always(self):
1054 return False
1054 return False
1055
1055
1056 def files(self):
1056 def files(self):
1057 return []
1057 return []
1058
1058
1059 def isexact(self):
1059 def isexact(self):
1060 return False
1060 return False
1061
1061
1062 def anypats(self):
1062 def anypats(self):
1063 return True
1063 return True
1064
1064
1065 def hash(self):
1065 def hash(self):
1066 sha1 = hashlib.sha1()
1066 sha1 = hashlib.sha1()
1067 sha1.update('negate')
1067 sha1.update('negate')
1068 sha1.update(_hashmatcher(self._matcher))
1068 sha1.update(_hashmatcher(self._matcher))
1069 return sha1.hexdigest()
1069 return sha1.hexdigest()
1070
1070
1071 def _hashmatcher(matcher):
1071 def _hashmatcher(matcher):
1072 if util.safehasattr(matcher, 'hash'):
1072 if util.safehasattr(matcher, 'hash'):
1073 return matcher.hash()
1073 return matcher.hash()
1074
1074
1075 sha1 = hashlib.sha1()
1075 sha1 = hashlib.sha1()
1076 sha1.update(repr(matcher))
1076 sha1.update(repr(matcher))
1077 return sha1.hexdigest()
1077 return sha1.hexdigest()
@@ -1,73 +1,73 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 sparse --include *.py
30 $ hg debugsparse --include *.py
31 $ ls
31 $ ls
32 data.py
32 data.py
33 $ hg sparse --clear-rules
33 $ hg debugsparse --clear-rules
34 $ ls
34 $ ls
35 base.sparse
35 base.sparse
36 data.py
36 data.py
37 index.html
37 index.html
38 readme.txt
38 readme.txt
39 webpage.sparse
39 webpage.sparse
40
40
41 Clear rules when there are excludes
41 Clear rules when there are excludes
42
42
43 $ hg sparse --exclude *.sparse
43 $ hg debugsparse --exclude *.sparse
44 $ ls
44 $ ls
45 data.py
45 data.py
46 index.html
46 index.html
47 readme.txt
47 readme.txt
48 $ hg sparse --clear-rules
48 $ hg debugsparse --clear-rules
49 $ ls
49 $ ls
50 base.sparse
50 base.sparse
51 data.py
51 data.py
52 index.html
52 index.html
53 readme.txt
53 readme.txt
54 webpage.sparse
54 webpage.sparse
55
55
56 Clearing rules should not alter profiles
56 Clearing rules should not alter profiles
57
57
58 $ hg sparse --enable-profile webpage.sparse
58 $ hg debugsparse --enable-profile webpage.sparse
59 $ ls
59 $ ls
60 base.sparse
60 base.sparse
61 index.html
61 index.html
62 webpage.sparse
62 webpage.sparse
63 $ hg sparse --include *.py
63 $ hg debugsparse --include *.py
64 $ ls
64 $ ls
65 base.sparse
65 base.sparse
66 data.py
66 data.py
67 index.html
67 index.html
68 webpage.sparse
68 webpage.sparse
69 $ hg sparse --clear-rules
69 $ hg debugsparse --clear-rules
70 $ ls
70 $ ls
71 base.sparse
71 base.sparse
72 index.html
72 index.html
73 webpage.sparse
73 webpage.sparse
@@ -1,186 +1,186 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 Import a rules file against a 'blank' sparse profile
28 Import a rules file against a 'blank' sparse profile
29
29
30 $ cat > $TESTTMP/rules_to_import <<EOF
30 $ cat > $TESTTMP/rules_to_import <<EOF
31 > [include]
31 > [include]
32 > *.py
32 > *.py
33 > EOF
33 > EOF
34 $ hg sparse --import-rules $TESTTMP/rules_to_import
34 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
35 $ ls
35 $ ls
36 data.py
36 data.py
37
37
38 $ hg sparse --reset
38 $ hg debugsparse --reset
39 $ rm .hg/sparse
39 $ rm .hg/sparse
40
40
41 $ cat > $TESTTMP/rules_to_import <<EOF
41 $ cat > $TESTTMP/rules_to_import <<EOF
42 > %include base.sparse
42 > %include base.sparse
43 > [include]
43 > [include]
44 > *.py
44 > *.py
45 > EOF
45 > EOF
46 $ hg sparse --import-rules $TESTTMP/rules_to_import
46 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
47 $ ls
47 $ ls
48 base.sparse
48 base.sparse
49 data.py
49 data.py
50 webpage.sparse
50 webpage.sparse
51
51
52 $ hg sparse --reset
52 $ hg debugsparse --reset
53 $ rm .hg/sparse
53 $ rm .hg/sparse
54
54
55 Start against an existing profile; rules *already active* should be ignored
55 Start against an existing profile; rules *already active* should be ignored
56
56
57 $ hg sparse --enable-profile webpage.sparse
57 $ hg debugsparse --enable-profile webpage.sparse
58 $ hg sparse --include *.py
58 $ hg debugsparse --include *.py
59 $ cat > $TESTTMP/rules_to_import <<EOF
59 $ cat > $TESTTMP/rules_to_import <<EOF
60 > %include base.sparse
60 > %include base.sparse
61 > [include]
61 > [include]
62 > *.html
62 > *.html
63 > *.txt
63 > *.txt
64 > [exclude]
64 > [exclude]
65 > *.py
65 > *.py
66 > EOF
66 > EOF
67 $ hg sparse --import-rules $TESTTMP/rules_to_import
67 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
68 $ ls
68 $ ls
69 base.sparse
69 base.sparse
70 index.html
70 index.html
71 readme.txt
71 readme.txt
72 webpage.sparse
72 webpage.sparse
73 $ cat .hg/sparse
73 $ cat .hg/sparse
74 %include webpage.sparse
74 %include webpage.sparse
75 [include]
75 [include]
76 *.py
76 *.py
77 *.txt
77 *.txt
78 [exclude]
78 [exclude]
79 *.py
79 *.py
80
80
81 $ hg sparse --reset
81 $ hg debugsparse --reset
82 $ rm .hg/sparse
82 $ rm .hg/sparse
83
83
84 Same tests, with -Tjson enabled to output summaries
84 Same tests, with -Tjson enabled to output summaries
85
85
86 $ cat > $TESTTMP/rules_to_import <<EOF
86 $ cat > $TESTTMP/rules_to_import <<EOF
87 > [include]
87 > [include]
88 > *.py
88 > *.py
89 > EOF
89 > EOF
90 $ hg sparse --import-rules $TESTTMP/rules_to_import -Tjson
90 $ hg debugsparse --import-rules $TESTTMP/rules_to_import -Tjson
91 [
91 [
92 {
92 {
93 "exclude_rules_added": 0,
93 "exclude_rules_added": 0,
94 "files_added": 0,
94 "files_added": 0,
95 "files_conflicting": 0,
95 "files_conflicting": 0,
96 "files_dropped": 4,
96 "files_dropped": 4,
97 "include_rules_added": 1,
97 "include_rules_added": 1,
98 "profiles_added": 0
98 "profiles_added": 0
99 }
99 }
100 ]
100 ]
101
101
102 $ hg sparse --reset
102 $ hg debugsparse --reset
103 $ rm .hg/sparse
103 $ rm .hg/sparse
104
104
105 $ cat > $TESTTMP/rules_to_import <<EOF
105 $ cat > $TESTTMP/rules_to_import <<EOF
106 > %include base.sparse
106 > %include base.sparse
107 > [include]
107 > [include]
108 > *.py
108 > *.py
109 > EOF
109 > EOF
110 $ hg sparse --import-rules $TESTTMP/rules_to_import -Tjson
110 $ hg debugsparse --import-rules $TESTTMP/rules_to_import -Tjson
111 [
111 [
112 {
112 {
113 "exclude_rules_added": 0,
113 "exclude_rules_added": 0,
114 "files_added": 0,
114 "files_added": 0,
115 "files_conflicting": 0,
115 "files_conflicting": 0,
116 "files_dropped": 2,
116 "files_dropped": 2,
117 "include_rules_added": 1,
117 "include_rules_added": 1,
118 "profiles_added": 1
118 "profiles_added": 1
119 }
119 }
120 ]
120 ]
121
121
122 $ hg sparse --reset
122 $ hg debugsparse --reset
123 $ rm .hg/sparse
123 $ rm .hg/sparse
124
124
125 $ hg sparse --enable-profile webpage.sparse
125 $ hg debugsparse --enable-profile webpage.sparse
126 $ hg sparse --include *.py
126 $ hg debugsparse --include *.py
127 $ cat > $TESTTMP/rules_to_import <<EOF
127 $ cat > $TESTTMP/rules_to_import <<EOF
128 > %include base.sparse
128 > %include base.sparse
129 > [include]
129 > [include]
130 > *.html
130 > *.html
131 > *.txt
131 > *.txt
132 > [exclude]
132 > [exclude]
133 > *.py
133 > *.py
134 > EOF
134 > EOF
135 $ hg sparse --import-rules $TESTTMP/rules_to_import -Tjson
135 $ hg debugsparse --import-rules $TESTTMP/rules_to_import -Tjson
136 [
136 [
137 {
137 {
138 "exclude_rules_added": 1,
138 "exclude_rules_added": 1,
139 "files_added": 1,
139 "files_added": 1,
140 "files_conflicting": 0,
140 "files_conflicting": 0,
141 "files_dropped": 1,
141 "files_dropped": 1,
142 "include_rules_added": 1,
142 "include_rules_added": 1,
143 "profiles_added": 0
143 "profiles_added": 0
144 }
144 }
145 ]
145 ]
146
146
147 If importing results in no new rules being added, no refresh should take place!
147 If importing results in no new rules being added, no refresh should take place!
148
148
149 $ cat > $TESTTMP/trap_sparse_refresh.py <<EOF
149 $ cat > $TESTTMP/trap_sparse_refresh.py <<EOF
150 > from mercurial import error, extensions
150 > from mercurial import error, extensions
151 > def extsetup(ui):
151 > def extsetup(ui):
152 > def abort_refresh(ui, *args):
152 > def abort_refresh(ui, *args):
153 > raise error.Abort('sparse._refresh called!')
153 > raise error.Abort('sparse._refresh called!')
154 > def sparseloaded(loaded):
154 > def sparseloaded(loaded):
155 > if not loaded:
155 > if not loaded:
156 > return
156 > return
157 > sparse = extensions.find('sparse')
157 > sparse = extensions.find('sparse')
158 > sparse._refresh = abort_refresh
158 > sparse._refresh = abort_refresh
159 > extensions.afterloaded('sparse', sparseloaded)
159 > extensions.afterloaded('sparse', sparseloaded)
160 > EOF
160 > EOF
161 $ cat >> $HGRCPATH <<EOF
161 $ cat >> $HGRCPATH <<EOF
162 > [extensions]
162 > [extensions]
163 > trap_sparse_refresh=$TESTTMP/trap_sparse_refresh.py
163 > trap_sparse_refresh=$TESTTMP/trap_sparse_refresh.py
164 > EOF
164 > EOF
165 $ cat > $TESTTMP/rules_to_import <<EOF
165 $ cat > $TESTTMP/rules_to_import <<EOF
166 > [include]
166 > [include]
167 > *.py
167 > *.py
168 > EOF
168 > EOF
169 $ hg sparse --import-rules $TESTTMP/rules_to_import
169 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
170
170
171 If an exception is raised during refresh, restore the existing rules again.
171 If an exception is raised during refresh, restore the existing rules again.
172
172
173 $ cat > $TESTTMP/rules_to_import <<EOF
173 $ cat > $TESTTMP/rules_to_import <<EOF
174 > [exclude]
174 > [exclude]
175 > *.html
175 > *.html
176 > EOF
176 > EOF
177 $ hg sparse --import-rules $TESTTMP/rules_to_import
177 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
178 abort: sparse._refresh called!
178 abort: sparse._refresh called!
179 [255]
179 [255]
180 $ cat .hg/sparse
180 $ cat .hg/sparse
181 %include webpage.sparse
181 %include webpage.sparse
182 [include]
182 [include]
183 *.py
183 *.py
184 *.txt
184 *.txt
185 [exclude]
185 [exclude]
186 *.py
186 *.py
@@ -1,62 +1,62 b''
1 test merging things outside of the sparse checkout
1 test merging things outside of the sparse checkout
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 > EOF
8 > EOF
9
9
10 $ echo foo > foo
10 $ echo foo > foo
11 $ echo bar > bar
11 $ echo bar > bar
12 $ hg add foo bar
12 $ hg add foo bar
13 $ hg commit -m initial
13 $ hg commit -m initial
14
14
15 $ hg branch feature
15 $ hg branch feature
16 marked working directory as branch feature
16 marked working directory as branch feature
17 (branches are permanent and global, did you want a bookmark?)
17 (branches are permanent and global, did you want a bookmark?)
18 $ echo bar2 >> bar
18 $ echo bar2 >> bar
19 $ hg commit -m 'feature - bar2'
19 $ hg commit -m 'feature - bar2'
20
20
21 $ hg update -q default
21 $ hg update -q default
22 $ hg sparse --exclude 'bar**'
22 $ hg debugsparse --exclude 'bar**'
23
23
24 $ hg merge feature
24 $ hg merge feature
25 temporarily included 1 file(s) in the sparse checkout for merging
25 temporarily included 1 file(s) in the sparse checkout for merging
26 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
26 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 (branch merge, don't forget to commit)
27 (branch merge, don't forget to commit)
28
28
29 Verify bar was merged temporarily
29 Verify bar was merged temporarily
30
30
31 $ ls
31 $ ls
32 bar
32 bar
33 foo
33 foo
34 $ hg status
34 $ hg status
35 M bar
35 M bar
36
36
37 Verify bar disappears automatically when the working copy becomes clean
37 Verify bar disappears automatically when the working copy becomes clean
38
38
39 $ hg commit -m "merged"
39 $ hg commit -m "merged"
40 cleaned up 1 temporarily added file(s) from the sparse checkout
40 cleaned up 1 temporarily added file(s) from the sparse checkout
41 $ hg status
41 $ hg status
42 $ ls
42 $ ls
43 foo
43 foo
44
44
45 $ hg cat -r . bar
45 $ hg cat -r . bar
46 bar
46 bar
47 bar2
47 bar2
48
48
49 Test merging things outside of the sparse checkout that are not in the working
49 Test merging things outside of the sparse checkout that are not in the working
50 copy
50 copy
51
51
52 $ hg strip -q -r . --config extensions.strip=
52 $ hg strip -q -r . --config extensions.strip=
53 $ hg up -q feature
53 $ hg up -q feature
54 $ touch branchonly
54 $ touch branchonly
55 $ hg ci -Aqm 'add branchonly'
55 $ hg ci -Aqm 'add branchonly'
56
56
57 $ hg up -q default
57 $ hg up -q default
58 $ hg sparse -X branchonly
58 $ hg debugsparse -X branchonly
59 $ hg merge feature
59 $ hg merge feature
60 temporarily included 2 file(s) in the sparse checkout for merging
60 temporarily included 2 file(s) in the sparse checkout for merging
61 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
61 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 (branch merge, don't forget to commit)
62 (branch merge, don't forget to commit)
@@ -1,272 +1,272 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 > 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 > webpage.sparse <<EOF
16 $ cat > webpage.sparse <<EOF
17 > # frontend sparse profile
17 > # frontend sparse profile
18 > [include]
18 > [include]
19 > *.html
19 > *.html
20 > EOF
20 > EOF
21 $ cat > backend.sparse <<EOF
21 $ cat > backend.sparse <<EOF
22 > # backend sparse profile
22 > # backend sparse profile
23 > [include]
23 > [include]
24 > *.py
24 > *.py
25 > EOF
25 > EOF
26 $ hg ci -Aqm 'initial'
26 $ hg ci -Aqm 'initial'
27
27
28 $ hg sparse --include '*.sparse'
28 $ hg debugsparse --include '*.sparse'
29
29
30 Verify enabling a single profile works
30 Verify enabling a single profile works
31
31
32 $ hg sparse --enable-profile webpage.sparse
32 $ hg debugsparse --enable-profile webpage.sparse
33 $ ls
33 $ ls
34 backend.sparse
34 backend.sparse
35 index.html
35 index.html
36 webpage.sparse
36 webpage.sparse
37
37
38 Verify enabling two profiles works
38 Verify enabling two profiles works
39
39
40 $ hg sparse --enable-profile backend.sparse
40 $ hg debugsparse --enable-profile backend.sparse
41 $ ls
41 $ ls
42 backend.sparse
42 backend.sparse
43 data.py
43 data.py
44 index.html
44 index.html
45 webpage.sparse
45 webpage.sparse
46
46
47 Verify disabling a profile works
47 Verify disabling a profile works
48
48
49 $ hg sparse --disable-profile webpage.sparse
49 $ hg debugsparse --disable-profile webpage.sparse
50 $ ls
50 $ ls
51 backend.sparse
51 backend.sparse
52 data.py
52 data.py
53 webpage.sparse
53 webpage.sparse
54
54
55 Verify that a profile is updated across multiple commits
55 Verify that a profile is updated across multiple commits
56
56
57 $ cat > webpage.sparse <<EOF
57 $ cat > webpage.sparse <<EOF
58 > # frontend sparse profile
58 > # frontend sparse profile
59 > [include]
59 > [include]
60 > *.html
60 > *.html
61 > EOF
61 > EOF
62 $ cat > backend.sparse <<EOF
62 $ cat > backend.sparse <<EOF
63 > # backend sparse profile
63 > # backend sparse profile
64 > [include]
64 > [include]
65 > *.py
65 > *.py
66 > *.txt
66 > *.txt
67 > EOF
67 > EOF
68
68
69 $ echo foo >> data.py
69 $ echo foo >> data.py
70
70
71 $ hg ci -m 'edit profile'
71 $ hg ci -m 'edit profile'
72 $ ls
72 $ ls
73 backend.sparse
73 backend.sparse
74 data.py
74 data.py
75 readme.txt
75 readme.txt
76 webpage.sparse
76 webpage.sparse
77
77
78 $ hg up -q 0
78 $ hg up -q 0
79 $ ls
79 $ ls
80 backend.sparse
80 backend.sparse
81 data.py
81 data.py
82 webpage.sparse
82 webpage.sparse
83
83
84 $ hg up -q 1
84 $ hg up -q 1
85 $ ls
85 $ ls
86 backend.sparse
86 backend.sparse
87 data.py
87 data.py
88 readme.txt
88 readme.txt
89 webpage.sparse
89 webpage.sparse
90
90
91 Introduce a conflicting .hgsparse change
91 Introduce a conflicting .hgsparse change
92
92
93 $ hg up -q 0
93 $ hg up -q 0
94 $ cat > backend.sparse <<EOF
94 $ cat > backend.sparse <<EOF
95 > # Different backend sparse profile
95 > # Different backend sparse profile
96 > [include]
96 > [include]
97 > *.html
97 > *.html
98 > EOF
98 > EOF
99 $ echo bar >> data.py
99 $ echo bar >> data.py
100
100
101 $ hg ci -qAm "edit profile other"
101 $ hg ci -qAm "edit profile other"
102 $ ls
102 $ ls
103 backend.sparse
103 backend.sparse
104 index.html
104 index.html
105 webpage.sparse
105 webpage.sparse
106
106
107 Verify conflicting merge pulls in the conflicting changes
107 Verify conflicting merge pulls in the conflicting changes
108
108
109 $ hg merge 1
109 $ hg merge 1
110 temporarily included 1 file(s) in the sparse checkout for merging
110 temporarily included 1 file(s) in the sparse checkout for merging
111 merging backend.sparse
111 merging backend.sparse
112 merging data.py
112 merging data.py
113 warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark')
113 warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark')
114 warning: conflicts while merging data.py! (edit, then use 'hg resolve --mark')
114 warning: conflicts while merging data.py! (edit, then use 'hg resolve --mark')
115 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
115 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
116 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
116 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
117 [1]
117 [1]
118
118
119 $ rm *.orig
119 $ rm *.orig
120 $ ls
120 $ ls
121 backend.sparse
121 backend.sparse
122 data.py
122 data.py
123 index.html
123 index.html
124 webpage.sparse
124 webpage.sparse
125
125
126 Verify resolving the merge removes the temporarily unioned files
126 Verify resolving the merge removes the temporarily unioned files
127
127
128 $ cat > backend.sparse <<EOF
128 $ cat > backend.sparse <<EOF
129 > # backend sparse profile
129 > # backend sparse profile
130 > [include]
130 > [include]
131 > *.html
131 > *.html
132 > *.txt
132 > *.txt
133 > EOF
133 > EOF
134 $ hg resolve -m backend.sparse
134 $ hg resolve -m backend.sparse
135
135
136 $ cat > data.py <<EOF
136 $ cat > data.py <<EOF
137 > x
137 > x
138 > foo
138 > foo
139 > bar
139 > bar
140 > EOF
140 > EOF
141 $ hg resolve -m data.py
141 $ hg resolve -m data.py
142 (no more unresolved files)
142 (no more unresolved files)
143
143
144 $ hg ci -qAm "merge profiles"
144 $ hg ci -qAm "merge profiles"
145 $ ls
145 $ ls
146 backend.sparse
146 backend.sparse
147 index.html
147 index.html
148 readme.txt
148 readme.txt
149 webpage.sparse
149 webpage.sparse
150
150
151 $ hg cat -r . data.py
151 $ hg cat -r . data.py
152 x
152 x
153 foo
153 foo
154 bar
154 bar
155
155
156 Verify stripping refreshes dirstate
156 Verify stripping refreshes dirstate
157
157
158 $ hg strip -q -r .
158 $ hg strip -q -r .
159 $ ls
159 $ ls
160 backend.sparse
160 backend.sparse
161 index.html
161 index.html
162 webpage.sparse
162 webpage.sparse
163
163
164 Verify rebase conflicts pulls in the conflicting changes
164 Verify rebase conflicts pulls in the conflicting changes
165
165
166 $ hg up -q 1
166 $ hg up -q 1
167 $ ls
167 $ ls
168 backend.sparse
168 backend.sparse
169 data.py
169 data.py
170 readme.txt
170 readme.txt
171 webpage.sparse
171 webpage.sparse
172
172
173 $ hg rebase -d 2
173 $ hg rebase -d 2
174 rebasing 1:a2b1de640a62 "edit profile"
174 rebasing 1:a2b1de640a62 "edit profile"
175 temporarily included 1 file(s) in the sparse checkout for merging
175 temporarily included 1 file(s) in the sparse checkout for merging
176 merging backend.sparse
176 merging backend.sparse
177 merging data.py
177 merging data.py
178 warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark')
178 warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark')
179 warning: conflicts while merging data.py! (edit, then use 'hg resolve --mark')
179 warning: conflicts while merging data.py! (edit, then use 'hg resolve --mark')
180 unresolved conflicts (see hg resolve, then hg rebase --continue)
180 unresolved conflicts (see hg resolve, then hg rebase --continue)
181 [1]
181 [1]
182 $ rm *.orig
182 $ rm *.orig
183 $ ls
183 $ ls
184 backend.sparse
184 backend.sparse
185 data.py
185 data.py
186 index.html
186 index.html
187 webpage.sparse
187 webpage.sparse
188
188
189 Verify resolving conflict removes the temporary files
189 Verify resolving conflict removes the temporary files
190
190
191 $ cat > backend.sparse <<EOF
191 $ cat > backend.sparse <<EOF
192 > [include]
192 > [include]
193 > *.html
193 > *.html
194 > *.txt
194 > *.txt
195 > EOF
195 > EOF
196 $ hg resolve -m backend.sparse
196 $ hg resolve -m backend.sparse
197
197
198 $ cat > data.py <<EOF
198 $ cat > data.py <<EOF
199 > x
199 > x
200 > foo
200 > foo
201 > bar
201 > bar
202 > EOF
202 > EOF
203 $ hg resolve -m data.py
203 $ hg resolve -m data.py
204 (no more unresolved files)
204 (no more unresolved files)
205 continue: hg rebase --continue
205 continue: hg rebase --continue
206
206
207 $ hg rebase -q --continue
207 $ hg rebase -q --continue
208 $ ls
208 $ ls
209 backend.sparse
209 backend.sparse
210 index.html
210 index.html
211 readme.txt
211 readme.txt
212 webpage.sparse
212 webpage.sparse
213
213
214 $ hg cat -r . data.py
214 $ hg cat -r . data.py
215 x
215 x
216 foo
216 foo
217 bar
217 bar
218
218
219 Test checking out a commit that does not contain the sparse profile. The
219 Test checking out a commit that does not contain the sparse profile. The
220 warning message can be suppressed by setting missingwarning = false in
220 warning message can be suppressed by setting missingwarning = false in
221 [sparse] section of your config:
221 [sparse] section of your config:
222
222
223 $ hg sparse --reset
223 $ hg debugsparse --reset
224 $ hg rm *.sparse
224 $ hg rm *.sparse
225 $ hg commit -m "delete profiles"
225 $ hg commit -m "delete profiles"
226 $ hg up -q ".^"
226 $ hg up -q ".^"
227 $ hg sparse --enable-profile backend.sparse
227 $ hg debugsparse --enable-profile backend.sparse
228 $ ls
228 $ ls
229 index.html
229 index.html
230 readme.txt
230 readme.txt
231 $ hg up tip | grep warning
231 $ hg up tip | grep warning
232 warning: sparse profile 'backend.sparse' not found in rev bfcb76de99cc - ignoring it
232 warning: sparse profile 'backend.sparse' not found in rev bfcb76de99cc - ignoring it
233 [1]
233 [1]
234 $ ls
234 $ ls
235 data.py
235 data.py
236 index.html
236 index.html
237 readme.txt
237 readme.txt
238 $ hg sparse --disable-profile backend.sparse | grep warning
238 $ hg debugsparse --disable-profile backend.sparse | grep warning
239 warning: sparse profile 'backend.sparse' not found in rev bfcb76de99cc - ignoring it
239 warning: sparse profile 'backend.sparse' not found in rev bfcb76de99cc - ignoring it
240 [1]
240 [1]
241 $ cat >> .hg/hgrc <<EOF
241 $ cat >> .hg/hgrc <<EOF
242 > [sparse]
242 > [sparse]
243 > missingwarning = false
243 > missingwarning = false
244 > EOF
244 > EOF
245 $ hg sparse --enable-profile backend.sparse
245 $ hg debugsparse --enable-profile backend.sparse
246
246
247 $ cd ..
247 $ cd ..
248
248
249 Test file permissions changing across a sparse profile change
249 Test file permissions changing across a sparse profile change
250 $ hg init sparseperm
250 $ hg init sparseperm
251 $ cd sparseperm
251 $ cd sparseperm
252 $ cat > .hg/hgrc <<EOF
252 $ cat > .hg/hgrc <<EOF
253 > [extensions]
253 > [extensions]
254 > sparse=
254 > sparse=
255 > EOF
255 > EOF
256 $ touch a b
256 $ touch a b
257 $ cat > .hgsparse <<EOF
257 $ cat > .hgsparse <<EOF
258 > a
258 > a
259 > EOF
259 > EOF
260 $ hg commit -Aqm 'initial'
260 $ hg commit -Aqm 'initial'
261 $ chmod a+x b
261 $ chmod a+x b
262 $ hg commit -qm 'make executable'
262 $ hg commit -qm 'make executable'
263 $ cat >> .hgsparse <<EOF
263 $ cat >> .hgsparse <<EOF
264 > b
264 > b
265 > EOF
265 > EOF
266 $ hg commit -qm 'update profile'
266 $ hg commit -qm 'update profile'
267 $ hg up -q 0
267 $ hg up -q 0
268 $ hg sparse --enable-profile .hgsparse
268 $ hg debugsparse --enable-profile .hgsparse
269 $ hg up -q 2
269 $ hg up -q 2
270 $ ls -l b
270 $ ls -l b
271 -rwxr-xr-x* b (glob)
271 -rwxr-xr-x* b (glob)
272
272
@@ -1,82 +1,82 b''
1 test sparse with --verbose and -T json
1 test sparse with --verbose and -T json
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 and --reset
21 Verify basic --include and --reset
22
22
23 $ hg up -q 0
23 $ hg up -q 0
24 $ hg sparse --include 'hide' -Tjson
24 $ hg debugsparse --include 'hide' -Tjson
25 [
25 [
26 {
26 {
27 "exclude_rules_added": 0,
27 "exclude_rules_added": 0,
28 "files_added": 0,
28 "files_added": 0,
29 "files_conflicting": 0,
29 "files_conflicting": 0,
30 "files_dropped": 1,
30 "files_dropped": 1,
31 "include_rules_added": 1,
31 "include_rules_added": 1,
32 "profiles_added": 0
32 "profiles_added": 0
33 }
33 }
34 ]
34 ]
35 $ hg sparse --clear-rules
35 $ hg debugsparse --clear-rules
36 $ hg sparse --include 'hide' --verbose
36 $ hg debugsparse --include 'hide' --verbose
37 removing show
37 removing show
38 Profile # change: 0
38 Profile # change: 0
39 Include rule # change: 1
39 Include rule # change: 1
40 Exclude rule # change: 0
40 Exclude rule # change: 0
41
41
42 $ hg sparse --reset -Tjson
42 $ hg debugsparse --reset -Tjson
43 [
43 [
44 {
44 {
45 "exclude_rules_added": 0,
45 "exclude_rules_added": 0,
46 "files_added": 1,
46 "files_added": 1,
47 "files_conflicting": 0,
47 "files_conflicting": 0,
48 "files_dropped": 0,
48 "files_dropped": 0,
49 "include_rules_added": -1,
49 "include_rules_added": -1,
50 "profiles_added": 0
50 "profiles_added": 0
51 }
51 }
52 ]
52 ]
53 $ hg sparse --include 'hide'
53 $ hg debugsparse --include 'hide'
54 $ hg sparse --reset --verbose
54 $ hg debugsparse --reset --verbose
55 getting show
55 getting show
56 Profile # change: 0
56 Profile # change: 0
57 Include rule # change: -1
57 Include rule # change: -1
58 Exclude rule # change: 0
58 Exclude rule # change: 0
59
59
60 Verifying that problematic files still allow us to see the deltas when forcing:
60 Verifying that problematic files still allow us to see the deltas when forcing:
61
61
62 $ hg sparse --include 'show*'
62 $ hg debugsparse --include 'show*'
63 $ touch hide
63 $ touch hide
64 $ hg sparse --delete 'show*' --force -Tjson
64 $ hg debugsparse --delete 'show*' --force -Tjson
65 pending changes to 'hide'
65 pending changes to 'hide'
66 [
66 [
67 {
67 {
68 "exclude_rules_added": 0,
68 "exclude_rules_added": 0,
69 "files_added": 0,
69 "files_added": 0,
70 "files_conflicting": 1,
70 "files_conflicting": 1,
71 "files_dropped": 0,
71 "files_dropped": 0,
72 "include_rules_added": -1,
72 "include_rules_added": -1,
73 "profiles_added": 0
73 "profiles_added": 0
74 }
74 }
75 ]
75 ]
76 $ hg sparse --include 'show*' --force
76 $ hg debugsparse --include 'show*' --force
77 pending changes to 'hide'
77 pending changes to 'hide'
78 $ hg sparse --delete 'show*' --force --verbose
78 $ hg debugsparse --delete 'show*' --force --verbose
79 pending changes to 'hide'
79 pending changes to 'hide'
80 Profile # change: 0
80 Profile # change: 0
81 Include rule # change: -1
81 Include rule # change: -1
82 Exclude rule # change: 0
82 Exclude rule # change: 0
@@ -1,369 +1,369 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 sparse --include 'hide'
24 $ hg debugsparse --include 'hide'
25 $ ls
25 $ ls
26 hide
26 hide
27
27
28 Absolute paths outside the repo should just be rejected
28 Absolute paths outside the repo should just be rejected
29
29
30 $ hg sparse --include /foo/bar
30 $ hg debugsparse --include /foo/bar
31 warning: paths cannot start with /, ignoring: ['/foo/bar']
31 warning: paths cannot start with /, ignoring: ['/foo/bar']
32 $ hg sparse --include '$TESTTMP/myrepo/hide'
32 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
33
33
34 $ hg sparse --include '/root'
34 $ hg debugsparse --include '/root'
35 warning: paths cannot start with /, ignoring: ['/root']
35 warning: paths cannot start with /, ignoring: ['/root']
36
36
37 Verify commiting while sparse includes other files
37 Verify commiting while sparse includes other files
38
38
39 $ echo z > hide
39 $ echo z > hide
40 $ hg ci -Aqm 'edit hide'
40 $ hg ci -Aqm 'edit hide'
41 $ ls
41 $ ls
42 hide
42 hide
43 $ hg manifest
43 $ hg manifest
44 hide
44 hide
45 show
45 show
46
46
47 Verify --reset brings files back
47 Verify --reset brings files back
48
48
49 $ hg sparse --reset
49 $ hg debugsparse --reset
50 $ ls
50 $ ls
51 hide
51 hide
52 show
52 show
53 $ cat hide
53 $ cat hide
54 z
54 z
55 $ cat show
55 $ cat show
56 a
56 a
57
57
58 Verify 'hg sparse' default output
58 Verify 'hg debugsparse' default output
59
59
60 $ hg up -q null
60 $ hg up -q null
61 $ hg sparse --include 'show*'
61 $ hg debugsparse --include 'show*'
62
62
63 $ hg sparse
63 $ hg debugsparse
64 [include]
64 [include]
65 show*
65 show*
66 [exclude]
66 [exclude]
67
67
68
68
69 Verify update only writes included files
69 Verify update only writes included files
70
70
71 $ hg up -q 0
71 $ hg up -q 0
72 $ ls
72 $ ls
73 show
73 show
74
74
75 $ hg up -q 1
75 $ hg up -q 1
76 $ ls
76 $ ls
77 show
77 show
78 show2
78 show2
79
79
80 Verify status only shows included files
80 Verify status only shows included files
81
81
82 $ touch hide
82 $ touch hide
83 $ touch hide3
83 $ touch hide3
84 $ echo c > show
84 $ echo c > show
85 $ hg status
85 $ hg status
86 M show
86 M show
87
87
88 Adding an excluded file should fail
88 Adding an excluded file should fail
89
89
90 $ hg add hide3
90 $ hg add hide3
91 abort: cannot add 'hide3' - it is outside the sparse checkout
91 abort: cannot add 'hide3' - it is outside the sparse checkout
92 (include file with `hg sparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
92 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
93 [255]
93 [255]
94
94
95 Verify deleting sparseness while a file has changes fails
95 Verify deleting sparseness while a file has changes fails
96
96
97 $ hg sparse --delete 'show*'
97 $ hg debugsparse --delete 'show*'
98 pending changes to 'hide'
98 pending changes to 'hide'
99 abort: cannot change sparseness due to pending changes (delete the files or use --force to bring them back dirty)
99 abort: cannot change sparseness due to pending changes (delete the files or use --force to bring them back dirty)
100 [255]
100 [255]
101
101
102 Verify deleting sparseness with --force brings back files
102 Verify deleting sparseness with --force brings back files
103
103
104 $ hg sparse --delete -f 'show*'
104 $ hg debugsparse --delete -f 'show*'
105 pending changes to 'hide'
105 pending changes to 'hide'
106 $ ls
106 $ ls
107 hide
107 hide
108 hide2
108 hide2
109 hide3
109 hide3
110 show
110 show
111 show2
111 show2
112 $ hg st
112 $ hg st
113 M hide
113 M hide
114 M show
114 M show
115 ? hide3
115 ? hide3
116
116
117 Verify editing sparseness fails if pending changes
117 Verify editing sparseness fails if pending changes
118
118
119 $ hg sparse --include 'show*'
119 $ hg debugsparse --include 'show*'
120 pending changes to 'hide'
120 pending changes to 'hide'
121 abort: could not update sparseness due to pending changes
121 abort: could not update sparseness due to pending changes
122 [255]
122 [255]
123
123
124 Verify adding sparseness hides files
124 Verify adding sparseness hides files
125
125
126 $ hg sparse --exclude -f 'hide*'
126 $ hg debugsparse --exclude -f 'hide*'
127 pending changes to 'hide'
127 pending changes to 'hide'
128 $ ls
128 $ ls
129 hide
129 hide
130 hide3
130 hide3
131 show
131 show
132 show2
132 show2
133 $ hg st
133 $ hg st
134 M show
134 M show
135
135
136 $ hg up -qC .
136 $ hg up -qC .
137 $ hg purge --all --config extensions.purge=
137 $ hg purge --all --config extensions.purge=
138 $ ls
138 $ ls
139 show
139 show
140 show2
140 show2
141
141
142 Verify rebase temporarily includes excluded files
142 Verify rebase temporarily includes excluded files
143
143
144 $ hg rebase -d 1 -r 2 --config extensions.rebase=
144 $ hg rebase -d 1 -r 2 --config extensions.rebase=
145 rebasing 2:b91df4f39e75 "edit hide" (tip)
145 rebasing 2:b91df4f39e75 "edit hide" (tip)
146 temporarily included 1 file(s) in the sparse checkout for merging
146 temporarily included 1 file(s) in the sparse checkout for merging
147 merging hide
147 merging hide
148 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
148 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
149 unresolved conflicts (see hg resolve, then hg rebase --continue)
149 unresolved conflicts (see hg resolve, then hg rebase --continue)
150 [1]
150 [1]
151
151
152 $ hg sparse
152 $ hg debugsparse
153 [include]
153 [include]
154
154
155 [exclude]
155 [exclude]
156 hide*
156 hide*
157
157
158 Temporarily Included Files (for merge/rebase):
158 Temporarily Included Files (for merge/rebase):
159 hide
159 hide
160
160
161 $ cat hide
161 $ cat hide
162 <<<<<<< dest: 39278f7c08a9 - test: two
162 <<<<<<< dest: 39278f7c08a9 - test: two
163 y
163 y
164 =======
164 =======
165 z
165 z
166 >>>>>>> source: b91df4f39e75 - test: edit hide
166 >>>>>>> source: b91df4f39e75 - test: edit hide
167
167
168 Verify aborting a rebase cleans up temporary files
168 Verify aborting a rebase cleans up temporary files
169
169
170 $ hg rebase --abort --config extensions.rebase=
170 $ hg rebase --abort --config extensions.rebase=
171 cleaned up 1 temporarily added file(s) from the sparse checkout
171 cleaned up 1 temporarily added file(s) from the sparse checkout
172 rebase aborted
172 rebase aborted
173 $ rm hide.orig
173 $ rm hide.orig
174
174
175 $ ls
175 $ ls
176 show
176 show
177 show2
177 show2
178
178
179 Verify merge fails if merging excluded files
179 Verify merge fails if merging excluded files
180
180
181 $ hg up -q 1
181 $ hg up -q 1
182 $ hg merge -r 2
182 $ hg merge -r 2
183 temporarily included 1 file(s) in the sparse checkout for merging
183 temporarily included 1 file(s) in the sparse checkout for merging
184 merging hide
184 merging hide
185 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
185 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
186 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
186 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
187 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
187 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
188 [1]
188 [1]
189 $ hg sparse
189 $ hg debugsparse
190 [include]
190 [include]
191
191
192 [exclude]
192 [exclude]
193 hide*
193 hide*
194
194
195 Temporarily Included Files (for merge/rebase):
195 Temporarily Included Files (for merge/rebase):
196 hide
196 hide
197
197
198 $ hg up -C .
198 $ hg up -C .
199 cleaned up 1 temporarily added file(s) from the sparse checkout
199 cleaned up 1 temporarily added file(s) from the sparse checkout
200 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
200 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
201 $ hg sparse
201 $ hg debugsparse
202 [include]
202 [include]
203
203
204 [exclude]
204 [exclude]
205 hide*
205 hide*
206
206
207
207
208 Verify strip -k resets dirstate correctly
208 Verify strip -k resets dirstate correctly
209
209
210 $ hg status
210 $ hg status
211 $ hg sparse
211 $ hg debugsparse
212 [include]
212 [include]
213
213
214 [exclude]
214 [exclude]
215 hide*
215 hide*
216
216
217 $ hg log -r . -T '{rev}\n' --stat
217 $ hg log -r . -T '{rev}\n' --stat
218 1
218 1
219 hide | 2 +-
219 hide | 2 +-
220 hide2 | 1 +
220 hide2 | 1 +
221 show | 2 +-
221 show | 2 +-
222 show2 | 1 +
222 show2 | 1 +
223 4 files changed, 4 insertions(+), 2 deletions(-)
223 4 files changed, 4 insertions(+), 2 deletions(-)
224
224
225 $ hg strip -r . -k
225 $ hg strip -r . -k
226 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/39278f7c08a9-ce59e002-backup.hg (glob)
226 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/39278f7c08a9-ce59e002-backup.hg (glob)
227 $ hg status
227 $ hg status
228 M show
228 M show
229 ? show2
229 ? show2
230
230
231 Verify rebase succeeds if all changed files are in sparse checkout
231 Verify rebase succeeds if all changed files are in sparse checkout
232
232
233 $ hg commit -Aqm "add show2"
233 $ hg commit -Aqm "add show2"
234 $ hg rebase -d 1 --config extensions.rebase=
234 $ hg rebase -d 1 --config extensions.rebase=
235 rebasing 2:bdde55290160 "add show2" (tip)
235 rebasing 2:bdde55290160 "add show2" (tip)
236 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/bdde55290160-216ed9c6-backup.hg (glob)
236 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/bdde55290160-216ed9c6-backup.hg (glob)
237
237
238 Verify log --sparse only shows commits that affect the sparse checkout
238 Verify log --sparse only shows commits that affect the sparse checkout
239
239
240 $ hg log -T '{rev} '
240 $ hg log -T '{rev} '
241 2 1 0 (no-eol)
241 2 1 0 (no-eol)
242 $ hg log --sparse -T '{rev} '
242 $ hg log --sparse -T '{rev} '
243 2 0 (no-eol)
243 2 0 (no-eol)
244
244
245 Test status on a file in a subdir
245 Test status on a file in a subdir
246
246
247 $ mkdir -p dir1/dir2
247 $ mkdir -p dir1/dir2
248 $ touch dir1/dir2/file
248 $ touch dir1/dir2/file
249 $ hg sparse -I dir1/dir2
249 $ hg debugsparse -I dir1/dir2
250 $ hg status
250 $ hg status
251 ? dir1/dir2/file
251 ? dir1/dir2/file
252
252
253 Test that add -s adds dirs to sparse profile
253 Test that add -s adds dirs to sparse profile
254
254
255 $ hg sparse --reset
255 $ hg debugsparse --reset
256 $ hg sparse --include empty
256 $ hg debugsparse --include empty
257 $ hg sparse
257 $ hg debugsparse
258 [include]
258 [include]
259 empty
259 empty
260 [exclude]
260 [exclude]
261
261
262
262
263
263
264 $ mkdir add
264 $ mkdir add
265 $ touch add/foo
265 $ touch add/foo
266 $ touch add/bar
266 $ touch add/bar
267 $ hg add add/foo
267 $ hg add add/foo
268 abort: cannot add 'add/foo' - it is outside the sparse checkout
268 abort: cannot add 'add/foo' - it is outside the sparse checkout
269 (include file with `hg sparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
269 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
270 [255]
270 [255]
271 $ hg add -s add/foo
271 $ hg add -s add/foo
272 $ hg st
272 $ hg st
273 A add/foo
273 A add/foo
274 ? add/bar
274 ? add/bar
275 $ hg sparse
275 $ hg debugsparse
276 [include]
276 [include]
277 add
277 add
278 empty
278 empty
279 [exclude]
279 [exclude]
280
280
281
281
282 $ hg add -s add/*
282 $ hg add -s add/*
283 add/foo already tracked!
283 add/foo already tracked!
284 $ hg st
284 $ hg st
285 A add/bar
285 A add/bar
286 A add/foo
286 A add/foo
287 $ hg sparse
287 $ hg debugsparse
288 [include]
288 [include]
289 add
289 add
290 empty
290 empty
291 [exclude]
291 [exclude]
292
292
293
293
294
294
295 $ cd ..
295 $ cd ..
296
296
297 Test non-sparse repos work while sparse is loaded
297 Test non-sparse repos work while sparse is loaded
298 $ hg init sparserepo
298 $ hg init sparserepo
299 $ hg init nonsparserepo
299 $ hg init nonsparserepo
300 $ cd sparserepo
300 $ cd sparserepo
301 $ cat > .hg/hgrc <<EOF
301 $ cat > .hg/hgrc <<EOF
302 > [extensions]
302 > [extensions]
303 > sparse=
303 > sparse=
304 > EOF
304 > EOF
305 $ cd ../nonsparserepo
305 $ cd ../nonsparserepo
306 $ echo x > x && hg add x && hg commit -qAm x
306 $ echo x > x && hg add x && hg commit -qAm x
307 $ cd ../sparserepo
307 $ cd ../sparserepo
308 $ hg clone ../nonsparserepo ../nonsparserepo2
308 $ hg clone ../nonsparserepo ../nonsparserepo2
309 updating to branch default
309 updating to branch default
310 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
310 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
311
311
312 Test debugrebuilddirstate
312 Test debugrebuilddirstate
313 $ cd ../sparserepo
313 $ cd ../sparserepo
314 $ touch included
314 $ touch included
315 $ touch excluded
315 $ touch excluded
316 $ hg add included excluded
316 $ hg add included excluded
317 $ hg commit -m 'a commit' -q
317 $ hg commit -m 'a commit' -q
318 $ cp .hg/dirstate ../dirstateboth
318 $ cp .hg/dirstate ../dirstateboth
319 $ hg sparse -X excluded
319 $ hg debugsparse -X excluded
320 $ cp ../dirstateboth .hg/dirstate
320 $ cp ../dirstateboth .hg/dirstate
321 $ hg debugrebuilddirstate
321 $ hg debugrebuilddirstate
322 $ hg debugdirstate
322 $ hg debugdirstate
323 n 0 -1 unset included
323 n 0 -1 unset included
324
324
325 Test debugdirstate --minimal where file is in the parent manifest but not the
325 Test debugdirstate --minimal where file is in the parent manifest but not the
326 dirstate
326 dirstate
327 $ hg sparse -X included
327 $ hg debugsparse -X included
328 $ hg debugdirstate
328 $ hg debugdirstate
329 $ cp .hg/dirstate ../dirstateallexcluded
329 $ cp .hg/dirstate ../dirstateallexcluded
330 $ hg sparse --reset
330 $ hg debugsparse --reset
331 $ hg sparse -X excluded
331 $ hg debugsparse -X excluded
332 $ cp ../dirstateallexcluded .hg/dirstate
332 $ cp ../dirstateallexcluded .hg/dirstate
333 $ touch includedadded
333 $ touch includedadded
334 $ hg add includedadded
334 $ hg add includedadded
335 $ hg debugdirstate --nodates
335 $ hg debugdirstate --nodates
336 a 0 -1 unset includedadded
336 a 0 -1 unset includedadded
337 $ hg debugrebuilddirstate --minimal
337 $ hg debugrebuilddirstate --minimal
338 $ hg debugdirstate --nodates
338 $ hg debugdirstate --nodates
339 n 0 -1 unset included
339 n 0 -1 unset included
340 a 0 -1 * includedadded (glob)
340 a 0 -1 * includedadded (glob)
341
341
342 Test debugdirstate --minimal where a file is not in parent manifest
342 Test debugdirstate --minimal where a file is not in parent manifest
343 but in the dirstate. This should take into account excluded files in the
343 but in the dirstate. This should take into account excluded files in the
344 manifest
344 manifest
345 $ cp ../dirstateboth .hg/dirstate
345 $ cp ../dirstateboth .hg/dirstate
346 $ touch includedadded
346 $ touch includedadded
347 $ hg add includedadded
347 $ hg add includedadded
348 $ touch excludednomanifest
348 $ touch excludednomanifest
349 $ hg add excludednomanifest
349 $ hg add excludednomanifest
350 $ cp .hg/dirstate ../moreexcluded
350 $ cp .hg/dirstate ../moreexcluded
351 $ hg forget excludednomanifest
351 $ hg forget excludednomanifest
352 $ rm excludednomanifest
352 $ rm excludednomanifest
353 $ hg sparse -X excludednomanifest
353 $ hg debugsparse -X excludednomanifest
354 $ cp ../moreexcluded .hg/dirstate
354 $ cp ../moreexcluded .hg/dirstate
355 $ hg manifest
355 $ hg manifest
356 excluded
356 excluded
357 included
357 included
358 We have files in the dirstate that are included and excluded. Some are in the
358 We have files in the dirstate that are included and excluded. Some are in the
359 manifest and some are not.
359 manifest and some are not.
360 $ hg debugdirstate --nodates
360 $ hg debugdirstate --nodates
361 n 644 0 * excluded (glob)
361 n 644 0 * excluded (glob)
362 a 0 -1 * excludednomanifest (glob)
362 a 0 -1 * excludednomanifest (glob)
363 n 644 0 * included (glob)
363 n 644 0 * included (glob)
364 a 0 -1 * includedadded (glob)
364 a 0 -1 * includedadded (glob)
365 $ hg debugrebuilddirstate --minimal
365 $ hg debugrebuilddirstate --minimal
366 $ hg debugdirstate --nodates
366 $ hg debugdirstate --nodates
367 n 644 0 * included (glob)
367 n 644 0 * included (glob)
368 a 0 -1 * includedadded (glob)
368 a 0 -1 * includedadded (glob)
369
369
General Comments 0
You need to be logged in to leave comments. Login now