##// END OF EJS Templates
sparse: use self instead of repo.dirstate...
Gregory Szorc -
r33372:4481f1fd default
parent child Browse files
Show More
@@ -1,446 +1,445 b''
1 # sparse.py - allow sparse checkouts of the working directory
1 # sparse.py - allow sparse checkouts of the working directory
2 #
2 #
3 # Copyright 2014 Facebook, Inc.
3 # Copyright 2014 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """allow sparse checkouts of the working directory (EXPERIMENTAL)
8 """allow sparse checkouts of the working directory (EXPERIMENTAL)
9
9
10 (This extension is not yet protected by backwards compatibility
10 (This extension is not yet protected by backwards compatibility
11 guarantees. Any aspect may break in future releases until this
11 guarantees. Any aspect may break in future releases until this
12 notice is removed.)
12 notice is removed.)
13
13
14 This extension allows the working directory to only consist of a
14 This extension allows the working directory to only consist of a
15 subset of files for the revision. This allows specific files or
15 subset of files for the revision. This allows specific files or
16 directories to be explicitly included or excluded. Many repository
16 directories to be explicitly included or excluded. Many repository
17 operations have performance proportional to the number of files in
17 operations have performance proportional to the number of files in
18 the working directory. So only realizing a subset of files in the
18 the working directory. So only realizing a subset of files in the
19 working directory can improve performance.
19 working directory can improve performance.
20
20
21 Sparse Config Files
21 Sparse Config Files
22 -------------------
22 -------------------
23
23
24 The set of files that are part of a sparse checkout are defined by
24 The set of files that are part of a sparse checkout are defined by
25 a sparse config file. The file defines 3 things: includes (files to
25 a sparse config file. The file defines 3 things: includes (files to
26 include in the sparse checkout), excludes (files to exclude from the
26 include in the sparse checkout), excludes (files to exclude from the
27 sparse checkout), and profiles (links to other config files).
27 sparse checkout), and profiles (links to other config files).
28
28
29 The file format is newline delimited. Empty lines and lines beginning
29 The file format is newline delimited. Empty lines and lines beginning
30 with ``#`` are ignored.
30 with ``#`` are ignored.
31
31
32 Lines beginning with ``%include `` denote another sparse config file
32 Lines beginning with ``%include `` denote another sparse config file
33 to include. e.g. ``%include tests.sparse``. The filename is relative
33 to include. e.g. ``%include tests.sparse``. The filename is relative
34 to the repository root.
34 to the repository root.
35
35
36 The special lines ``[include]`` and ``[exclude]`` denote the section
36 The special lines ``[include]`` and ``[exclude]`` denote the section
37 for includes and excludes that follow, respectively. It is illegal to
37 for includes and excludes that follow, respectively. It is illegal to
38 have ``[include]`` after ``[exclude]``. If no sections are defined,
38 have ``[include]`` after ``[exclude]``. If no sections are defined,
39 entries are assumed to be in the ``[include]`` section.
39 entries are assumed to be in the ``[include]`` section.
40
40
41 Non-special lines resemble file patterns to be added to either includes
41 Non-special lines resemble file patterns to be added to either includes
42 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
42 or excludes. The syntax of these lines is documented by :hg:`help patterns`.
43 Patterns are interpreted as ``glob:`` by default and match against the
43 Patterns are interpreted as ``glob:`` by default and match against the
44 root of the repository.
44 root of the repository.
45
45
46 Exclusion patterns take precedence over inclusion patterns. So even
46 Exclusion patterns take precedence over inclusion patterns. So even
47 if a file is explicitly included, an ``[exclude]`` entry can remove it.
47 if a file is explicitly included, an ``[exclude]`` entry can remove it.
48
48
49 For example, say you have a repository with 3 directories, ``frontend/``,
49 For example, say you have a repository with 3 directories, ``frontend/``,
50 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
50 ``backend/``, and ``tools/``. ``frontend/`` and ``backend/`` correspond
51 to different projects and it is uncommon for someone working on one
51 to different projects and it is uncommon for someone working on one
52 to need the files for the other. But ``tools/`` contains files shared
52 to need the files for the other. But ``tools/`` contains files shared
53 between both projects. Your sparse config files may resemble::
53 between both projects. Your sparse config files may resemble::
54
54
55 # frontend.sparse
55 # frontend.sparse
56 frontend/**
56 frontend/**
57 tools/**
57 tools/**
58
58
59 # backend.sparse
59 # backend.sparse
60 backend/**
60 backend/**
61 tools/**
61 tools/**
62
62
63 Say the backend grows in size. Or there's a directory with thousands
63 Say the backend grows in size. Or there's a directory with thousands
64 of files you wish to exclude. You can modify the profile to exclude
64 of files you wish to exclude. You can modify the profile to exclude
65 certain files::
65 certain files::
66
66
67 [include]
67 [include]
68 backend/**
68 backend/**
69 tools/**
69 tools/**
70
70
71 [exclude]
71 [exclude]
72 tools/tests/**
72 tools/tests/**
73 """
73 """
74
74
75 from __future__ import absolute_import
75 from __future__ import absolute_import
76
76
77 from mercurial.i18n import _
77 from mercurial.i18n import _
78 from mercurial import (
78 from mercurial import (
79 cmdutil,
79 cmdutil,
80 commands,
80 commands,
81 dirstate,
81 dirstate,
82 error,
82 error,
83 extensions,
83 extensions,
84 hg,
84 hg,
85 localrepo,
85 localrepo,
86 match as matchmod,
86 match as matchmod,
87 registrar,
87 registrar,
88 sparse,
88 sparse,
89 util,
89 util,
90 )
90 )
91
91
92 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
92 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
93 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
93 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
94 # be specifying the version(s) of Mercurial they are tested with, or
94 # be specifying the version(s) of Mercurial they are tested with, or
95 # leave the attribute unspecified.
95 # leave the attribute unspecified.
96 testedwith = 'ships-with-hg-core'
96 testedwith = 'ships-with-hg-core'
97
97
98 cmdtable = {}
98 cmdtable = {}
99 command = registrar.command(cmdtable)
99 command = registrar.command(cmdtable)
100
100
101 def extsetup(ui):
101 def extsetup(ui):
102 sparse.enabled = True
102 sparse.enabled = True
103
103
104 _setupclone(ui)
104 _setupclone(ui)
105 _setuplog(ui)
105 _setuplog(ui)
106 _setupadd(ui)
106 _setupadd(ui)
107 _setupdirstate(ui)
107 _setupdirstate(ui)
108
108
109 def reposetup(ui, repo):
109 def reposetup(ui, repo):
110 if not util.safehasattr(repo, 'dirstate'):
110 if not util.safehasattr(repo, 'dirstate'):
111 return
111 return
112
112
113 if 'dirstate' in repo._filecache:
113 if 'dirstate' in repo._filecache:
114 repo.dirstate.repo = repo
114 repo.dirstate.repo = repo
115
115
116 def replacefilecache(cls, propname, replacement):
116 def replacefilecache(cls, propname, replacement):
117 """Replace a filecache property with a new class. This allows changing the
117 """Replace a filecache property with a new class. This allows changing the
118 cache invalidation condition."""
118 cache invalidation condition."""
119 origcls = cls
119 origcls = cls
120 assert callable(replacement)
120 assert callable(replacement)
121 while cls is not object:
121 while cls is not object:
122 if propname in cls.__dict__:
122 if propname in cls.__dict__:
123 orig = cls.__dict__[propname]
123 orig = cls.__dict__[propname]
124 setattr(cls, propname, replacement(orig))
124 setattr(cls, propname, replacement(orig))
125 break
125 break
126 cls = cls.__bases__[0]
126 cls = cls.__bases__[0]
127
127
128 if cls is object:
128 if cls is object:
129 raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
129 raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
130 propname))
130 propname))
131
131
132 def _setuplog(ui):
132 def _setuplog(ui):
133 entry = commands.table['^log|history']
133 entry = commands.table['^log|history']
134 entry[1].append(('', 'sparse', None,
134 entry[1].append(('', 'sparse', None,
135 "limit to changesets affecting the sparse checkout"))
135 "limit to changesets affecting the sparse checkout"))
136
136
137 def _logrevs(orig, repo, opts):
137 def _logrevs(orig, repo, opts):
138 revs = orig(repo, opts)
138 revs = orig(repo, opts)
139 if opts.get('sparse'):
139 if opts.get('sparse'):
140 sparsematch = sparse.matcher(repo)
140 sparsematch = sparse.matcher(repo)
141 def ctxmatch(rev):
141 def ctxmatch(rev):
142 ctx = repo[rev]
142 ctx = repo[rev]
143 return any(f for f in ctx.files() if sparsematch(f))
143 return any(f for f in ctx.files() if sparsematch(f))
144 revs = revs.filter(ctxmatch)
144 revs = revs.filter(ctxmatch)
145 return revs
145 return revs
146 extensions.wrapfunction(cmdutil, '_logrevs', _logrevs)
146 extensions.wrapfunction(cmdutil, '_logrevs', _logrevs)
147
147
148 def _clonesparsecmd(orig, ui, repo, *args, **opts):
148 def _clonesparsecmd(orig, ui, repo, *args, **opts):
149 include_pat = opts.get('include')
149 include_pat = opts.get('include')
150 exclude_pat = opts.get('exclude')
150 exclude_pat = opts.get('exclude')
151 enableprofile_pat = opts.get('enable_profile')
151 enableprofile_pat = opts.get('enable_profile')
152 include = exclude = enableprofile = False
152 include = exclude = enableprofile = False
153 if include_pat:
153 if include_pat:
154 pat = include_pat
154 pat = include_pat
155 include = True
155 include = True
156 if exclude_pat:
156 if exclude_pat:
157 pat = exclude_pat
157 pat = exclude_pat
158 exclude = True
158 exclude = True
159 if enableprofile_pat:
159 if enableprofile_pat:
160 pat = enableprofile_pat
160 pat = enableprofile_pat
161 enableprofile = True
161 enableprofile = True
162 if sum([include, exclude, enableprofile]) > 1:
162 if sum([include, exclude, enableprofile]) > 1:
163 raise error.Abort(_("too many flags specified."))
163 raise error.Abort(_("too many flags specified."))
164 if include or exclude or enableprofile:
164 if include or exclude or enableprofile:
165 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
165 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
166 _config(self.ui, self.unfiltered(), pat, {}, include=include,
166 _config(self.ui, self.unfiltered(), pat, {}, include=include,
167 exclude=exclude, enableprofile=enableprofile)
167 exclude=exclude, enableprofile=enableprofile)
168 return orig(self, node, overwrite, *args, **kwargs)
168 return orig(self, node, overwrite, *args, **kwargs)
169 extensions.wrapfunction(hg, 'updaterepo', clonesparse)
169 extensions.wrapfunction(hg, 'updaterepo', clonesparse)
170 return orig(ui, repo, *args, **opts)
170 return orig(ui, repo, *args, **opts)
171
171
172 def _setupclone(ui):
172 def _setupclone(ui):
173 entry = commands.table['^clone']
173 entry = commands.table['^clone']
174 entry[1].append(('', 'enable-profile', [],
174 entry[1].append(('', 'enable-profile', [],
175 'enable a sparse profile'))
175 'enable a sparse profile'))
176 entry[1].append(('', 'include', [],
176 entry[1].append(('', 'include', [],
177 'include sparse pattern'))
177 'include sparse pattern'))
178 entry[1].append(('', 'exclude', [],
178 entry[1].append(('', 'exclude', [],
179 'exclude sparse pattern'))
179 'exclude sparse pattern'))
180 extensions.wrapcommand(commands.table, 'clone', _clonesparsecmd)
180 extensions.wrapcommand(commands.table, 'clone', _clonesparsecmd)
181
181
182 def _setupadd(ui):
182 def _setupadd(ui):
183 entry = commands.table['^add']
183 entry = commands.table['^add']
184 entry[1].append(('s', 'sparse', None,
184 entry[1].append(('s', 'sparse', None,
185 'also include directories of added files in sparse config'))
185 'also include directories of added files in sparse config'))
186
186
187 def _add(orig, ui, repo, *pats, **opts):
187 def _add(orig, ui, repo, *pats, **opts):
188 if opts.get('sparse'):
188 if opts.get('sparse'):
189 dirs = set()
189 dirs = set()
190 for pat in pats:
190 for pat in pats:
191 dirname, basename = util.split(pat)
191 dirname, basename = util.split(pat)
192 dirs.add(dirname)
192 dirs.add(dirname)
193 _config(ui, repo, list(dirs), opts, include=True)
193 _config(ui, repo, list(dirs), opts, include=True)
194 return orig(ui, repo, *pats, **opts)
194 return orig(ui, repo, *pats, **opts)
195
195
196 extensions.wrapcommand(commands.table, 'add', _add)
196 extensions.wrapcommand(commands.table, 'add', _add)
197
197
198 def _setupdirstate(ui):
198 def _setupdirstate(ui):
199 """Modify the dirstate to prevent stat'ing excluded files,
199 """Modify the dirstate to prevent stat'ing excluded files,
200 and to prevent modifications to files outside the checkout.
200 and to prevent modifications to files outside the checkout.
201 """
201 """
202
202
203 def _dirstate(orig, repo):
203 def _dirstate(orig, repo):
204 dirstate = orig(repo)
204 dirstate = orig(repo)
205 dirstate.repo = repo
205 dirstate.repo = repo
206 return dirstate
206 return dirstate
207 extensions.wrapfunction(
207 extensions.wrapfunction(
208 localrepo.localrepository.dirstate, 'func', _dirstate)
208 localrepo.localrepository.dirstate, 'func', _dirstate)
209
209
210 # The atrocity below is needed to wrap dirstate._ignore. It is a cached
210 # The atrocity below is needed to wrap dirstate._ignore. It is a cached
211 # property, which means normal function wrapping doesn't work.
211 # property, which means normal function wrapping doesn't work.
212 class ignorewrapper(object):
212 class ignorewrapper(object):
213 def __init__(self, orig):
213 def __init__(self, orig):
214 self.orig = orig
214 self.orig = orig
215 self.origignore = None
215 self.origignore = None
216 self.func = None
216 self.func = None
217 self.sparsematch = None
217 self.sparsematch = None
218
218
219 def __get__(self, obj, type=None):
219 def __get__(self, obj, type=None):
220 repo = obj.repo
220 repo = obj.repo
221 origignore = self.orig.__get__(obj)
221 origignore = self.orig.__get__(obj)
222
222
223 sparsematch = sparse.matcher(repo)
223 sparsematch = sparse.matcher(repo)
224 if sparsematch.always():
224 if sparsematch.always():
225 return origignore
225 return origignore
226
226
227 if self.sparsematch != sparsematch or self.origignore != origignore:
227 if self.sparsematch != sparsematch or self.origignore != origignore:
228 self.func = matchmod.unionmatcher([
228 self.func = matchmod.unionmatcher([
229 origignore, matchmod.negatematcher(sparsematch)])
229 origignore, matchmod.negatematcher(sparsematch)])
230 self.sparsematch = sparsematch
230 self.sparsematch = sparsematch
231 self.origignore = origignore
231 self.origignore = origignore
232 return self.func
232 return self.func
233
233
234 def __set__(self, obj, value):
234 def __set__(self, obj, value):
235 return self.orig.__set__(obj, value)
235 return self.orig.__set__(obj, value)
236
236
237 def __delete__(self, obj):
237 def __delete__(self, obj):
238 return self.orig.__delete__(obj)
238 return self.orig.__delete__(obj)
239
239
240 replacefilecache(dirstate.dirstate, '_ignore', ignorewrapper)
240 replacefilecache(dirstate.dirstate, '_ignore', ignorewrapper)
241
241
242 # dirstate.rebuild should not add non-matching files
242 # dirstate.rebuild should not add non-matching files
243 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
243 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
244 matcher = sparse.matcher(self.repo)
244 matcher = sparse.matcher(self.repo)
245 if not matcher.always():
245 if not matcher.always():
246 allfiles = allfiles.matches(matcher)
246 allfiles = allfiles.matches(matcher)
247 if changedfiles:
247 if changedfiles:
248 changedfiles = [f for f in changedfiles if matcher(f)]
248 changedfiles = [f for f in changedfiles if matcher(f)]
249
249
250 if changedfiles is not None:
250 if changedfiles is not None:
251 # In _rebuild, these files will be deleted from the dirstate
251 # In _rebuild, these files will be deleted from the dirstate
252 # when they are not found to be in allfiles
252 # when they are not found to be in allfiles
253 dirstatefilestoremove = set(f for f in self if not matcher(f))
253 dirstatefilestoremove = set(f for f in self if not matcher(f))
254 changedfiles = dirstatefilestoremove.union(changedfiles)
254 changedfiles = dirstatefilestoremove.union(changedfiles)
255
255
256 return orig(self, parent, allfiles, changedfiles)
256 return orig(self, parent, allfiles, changedfiles)
257 extensions.wrapfunction(dirstate.dirstate, 'rebuild', _rebuild)
257 extensions.wrapfunction(dirstate.dirstate, 'rebuild', _rebuild)
258
258
259 # Prevent adding files that are outside the sparse checkout
259 # Prevent adding files that are outside the sparse checkout
260 editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
260 editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
261 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
261 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
262 '`hg add -s <file>` to include file directory while adding')
262 '`hg add -s <file>` to include file directory while adding')
263 for func in editfuncs:
263 for func in editfuncs:
264 def _wrapper(orig, self, *args):
264 def _wrapper(orig, self, *args):
265 repo = self.repo
265 repo = self.repo
266 sparsematch = sparse.matcher(repo)
266 sparsematch = sparse.matcher(repo)
267 if not sparsematch.always():
267 if not sparsematch.always():
268 dirstate = repo.dirstate
269 for f in args:
268 for f in args:
270 if (f is not None and not sparsematch(f) and
269 if (f is not None and not sparsematch(f) and
271 f not in dirstate):
270 f not in self):
272 raise error.Abort(_("cannot add '%s' - it is outside "
271 raise error.Abort(_("cannot add '%s' - it is outside "
273 "the sparse checkout") % f,
272 "the sparse checkout") % f,
274 hint=hint)
273 hint=hint)
275 return orig(self, *args)
274 return orig(self, *args)
276 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
275 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
277
276
278 @command('^debugsparse', [
277 @command('^debugsparse', [
279 ('I', 'include', False, _('include files in the sparse checkout')),
278 ('I', 'include', False, _('include files in the sparse checkout')),
280 ('X', 'exclude', False, _('exclude files in the sparse checkout')),
279 ('X', 'exclude', False, _('exclude files in the sparse checkout')),
281 ('d', 'delete', False, _('delete an include/exclude rule')),
280 ('d', 'delete', False, _('delete an include/exclude rule')),
282 ('f', 'force', False, _('allow changing rules even with pending changes')),
281 ('f', 'force', False, _('allow changing rules even with pending changes')),
283 ('', 'enable-profile', False, _('enables the specified profile')),
282 ('', 'enable-profile', False, _('enables the specified profile')),
284 ('', 'disable-profile', False, _('disables the specified profile')),
283 ('', 'disable-profile', False, _('disables the specified profile')),
285 ('', 'import-rules', False, _('imports rules from a file')),
284 ('', 'import-rules', False, _('imports rules from a file')),
286 ('', 'clear-rules', False, _('clears local include/exclude rules')),
285 ('', 'clear-rules', False, _('clears local include/exclude rules')),
287 ('', 'refresh', False, _('updates the working after sparseness changes')),
286 ('', 'refresh', False, _('updates the working after sparseness changes')),
288 ('', 'reset', False, _('makes the repo full again')),
287 ('', 'reset', False, _('makes the repo full again')),
289 ] + commands.templateopts,
288 ] + commands.templateopts,
290 _('[--OPTION] PATTERN...'))
289 _('[--OPTION] PATTERN...'))
291 def debugsparse(ui, repo, *pats, **opts):
290 def debugsparse(ui, repo, *pats, **opts):
292 """make the current checkout sparse, or edit the existing checkout
291 """make the current checkout sparse, or edit the existing checkout
293
292
294 The sparse command is used to make the current checkout sparse.
293 The sparse command is used to make the current checkout sparse.
295 This means files that don't meet the sparse condition will not be
294 This means files that don't meet the sparse condition will not be
296 written to disk, or show up in any working copy operations. It does
295 written to disk, or show up in any working copy operations. It does
297 not affect files in history in any way.
296 not affect files in history in any way.
298
297
299 Passing no arguments prints the currently applied sparse rules.
298 Passing no arguments prints the currently applied sparse rules.
300
299
301 --include and --exclude are used to add and remove files from the sparse
300 --include and --exclude are used to add and remove files from the sparse
302 checkout. The effects of adding an include or exclude rule are applied
301 checkout. The effects of adding an include or exclude rule are applied
303 immediately. If applying the new rule would cause a file with pending
302 immediately. If applying the new rule would cause a file with pending
304 changes to be added or removed, the command will fail. Pass --force to
303 changes to be added or removed, the command will fail. Pass --force to
305 force a rule change even with pending changes (the changes on disk will
304 force a rule change even with pending changes (the changes on disk will
306 be preserved).
305 be preserved).
307
306
308 --delete removes an existing include/exclude rule. The effects are
307 --delete removes an existing include/exclude rule. The effects are
309 immediate.
308 immediate.
310
309
311 --refresh refreshes the files on disk based on the sparse rules. This is
310 --refresh refreshes the files on disk based on the sparse rules. This is
312 only necessary if .hg/sparse was changed by hand.
311 only necessary if .hg/sparse was changed by hand.
313
312
314 --enable-profile and --disable-profile accept a path to a .hgsparse file.
313 --enable-profile and --disable-profile accept a path to a .hgsparse file.
315 This allows defining sparse checkouts and tracking them inside the
314 This allows defining sparse checkouts and tracking them inside the
316 repository. This is useful for defining commonly used sparse checkouts for
315 repository. This is useful for defining commonly used sparse checkouts for
317 many people to use. As the profile definition changes over time, the sparse
316 many people to use. As the profile definition changes over time, the sparse
318 checkout will automatically be updated appropriately, depending on which
317 checkout will automatically be updated appropriately, depending on which
319 changeset is checked out. Changes to .hgsparse are not applied until they
318 changeset is checked out. Changes to .hgsparse are not applied until they
320 have been committed.
319 have been committed.
321
320
322 --import-rules accepts a path to a file containing rules in the .hgsparse
321 --import-rules accepts a path to a file containing rules in the .hgsparse
323 format, allowing you to add --include, --exclude and --enable-profile rules
322 format, allowing you to add --include, --exclude and --enable-profile rules
324 in bulk. Like the --include, --exclude and --enable-profile switches, the
323 in bulk. Like the --include, --exclude and --enable-profile switches, the
325 changes are applied immediately.
324 changes are applied immediately.
326
325
327 --clear-rules removes all local include and exclude rules, while leaving
326 --clear-rules removes all local include and exclude rules, while leaving
328 any enabled profiles in place.
327 any enabled profiles in place.
329
328
330 Returns 0 if editing the sparse checkout succeeds.
329 Returns 0 if editing the sparse checkout succeeds.
331 """
330 """
332 include = opts.get('include')
331 include = opts.get('include')
333 exclude = opts.get('exclude')
332 exclude = opts.get('exclude')
334 force = opts.get('force')
333 force = opts.get('force')
335 enableprofile = opts.get('enable_profile')
334 enableprofile = opts.get('enable_profile')
336 disableprofile = opts.get('disable_profile')
335 disableprofile = opts.get('disable_profile')
337 importrules = opts.get('import_rules')
336 importrules = opts.get('import_rules')
338 clearrules = opts.get('clear_rules')
337 clearrules = opts.get('clear_rules')
339 delete = opts.get('delete')
338 delete = opts.get('delete')
340 refresh = opts.get('refresh')
339 refresh = opts.get('refresh')
341 reset = opts.get('reset')
340 reset = opts.get('reset')
342 count = sum([include, exclude, enableprofile, disableprofile, delete,
341 count = sum([include, exclude, enableprofile, disableprofile, delete,
343 importrules, refresh, clearrules, reset])
342 importrules, refresh, clearrules, reset])
344 if count > 1:
343 if count > 1:
345 raise error.Abort(_("too many flags specified"))
344 raise error.Abort(_("too many flags specified"))
346
345
347 if count == 0:
346 if count == 0:
348 if repo.vfs.exists('sparse'):
347 if repo.vfs.exists('sparse'):
349 ui.status(repo.vfs.read("sparse") + "\n")
348 ui.status(repo.vfs.read("sparse") + "\n")
350 temporaryincludes = sparse.readtemporaryincludes(repo)
349 temporaryincludes = sparse.readtemporaryincludes(repo)
351 if temporaryincludes:
350 if temporaryincludes:
352 ui.status(_("Temporarily Included Files (for merge/rebase):\n"))
351 ui.status(_("Temporarily Included Files (for merge/rebase):\n"))
353 ui.status(("\n".join(temporaryincludes) + "\n"))
352 ui.status(("\n".join(temporaryincludes) + "\n"))
354 else:
353 else:
355 ui.status(_('repo is not sparse\n'))
354 ui.status(_('repo is not sparse\n'))
356 return
355 return
357
356
358 if include or exclude or delete or reset or enableprofile or disableprofile:
357 if include or exclude or delete or reset or enableprofile or disableprofile:
359 _config(ui, repo, pats, opts, include=include, exclude=exclude,
358 _config(ui, repo, pats, opts, include=include, exclude=exclude,
360 reset=reset, delete=delete, enableprofile=enableprofile,
359 reset=reset, delete=delete, enableprofile=enableprofile,
361 disableprofile=disableprofile, force=force)
360 disableprofile=disableprofile, force=force)
362
361
363 if importrules:
362 if importrules:
364 sparse.importfromfiles(repo, opts, pats, force=force)
363 sparse.importfromfiles(repo, opts, pats, force=force)
365
364
366 if clearrules:
365 if clearrules:
367 sparse.clearrules(repo, force=force)
366 sparse.clearrules(repo, force=force)
368
367
369 if refresh:
368 if refresh:
370 try:
369 try:
371 wlock = repo.wlock()
370 wlock = repo.wlock()
372 fcounts = map(
371 fcounts = map(
373 len,
372 len,
374 sparse.refreshwdir(repo, repo.status(), sparse.matcher(repo),
373 sparse.refreshwdir(repo, repo.status(), sparse.matcher(repo),
375 force=force))
374 force=force))
376 sparse.printchanges(ui, opts, added=fcounts[0], dropped=fcounts[1],
375 sparse.printchanges(ui, opts, added=fcounts[0], dropped=fcounts[1],
377 conflicting=fcounts[2])
376 conflicting=fcounts[2])
378 finally:
377 finally:
379 wlock.release()
378 wlock.release()
380
379
381 def _config(ui, repo, pats, opts, include=False, exclude=False, reset=False,
380 def _config(ui, repo, pats, opts, include=False, exclude=False, reset=False,
382 delete=False, enableprofile=False, disableprofile=False,
381 delete=False, enableprofile=False, disableprofile=False,
383 force=False):
382 force=False):
384 """
383 """
385 Perform a sparse config update. Only one of the kwargs may be specified.
384 Perform a sparse config update. Only one of the kwargs may be specified.
386 """
385 """
387 wlock = repo.wlock()
386 wlock = repo.wlock()
388 try:
387 try:
389 oldsparsematch = sparse.matcher(repo)
388 oldsparsematch = sparse.matcher(repo)
390
389
391 raw = repo.vfs.tryread('sparse')
390 raw = repo.vfs.tryread('sparse')
392 if raw:
391 if raw:
393 oldinclude, oldexclude, oldprofiles = map(
392 oldinclude, oldexclude, oldprofiles = map(
394 set, sparse.parseconfig(ui, raw))
393 set, sparse.parseconfig(ui, raw))
395 else:
394 else:
396 oldinclude = set()
395 oldinclude = set()
397 oldexclude = set()
396 oldexclude = set()
398 oldprofiles = set()
397 oldprofiles = set()
399
398
400 try:
399 try:
401 if reset:
400 if reset:
402 newinclude = set()
401 newinclude = set()
403 newexclude = set()
402 newexclude = set()
404 newprofiles = set()
403 newprofiles = set()
405 else:
404 else:
406 newinclude = set(oldinclude)
405 newinclude = set(oldinclude)
407 newexclude = set(oldexclude)
406 newexclude = set(oldexclude)
408 newprofiles = set(oldprofiles)
407 newprofiles = set(oldprofiles)
409
408
410 oldstatus = repo.status()
409 oldstatus = repo.status()
411
410
412 if any(pat.startswith('/') for pat in pats):
411 if any(pat.startswith('/') for pat in pats):
413 ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
412 ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
414 % ([pat for pat in pats if pat.startswith('/')]))
413 % ([pat for pat in pats if pat.startswith('/')]))
415 elif include:
414 elif include:
416 newinclude.update(pats)
415 newinclude.update(pats)
417 elif exclude:
416 elif exclude:
418 newexclude.update(pats)
417 newexclude.update(pats)
419 elif enableprofile:
418 elif enableprofile:
420 newprofiles.update(pats)
419 newprofiles.update(pats)
421 elif disableprofile:
420 elif disableprofile:
422 newprofiles.difference_update(pats)
421 newprofiles.difference_update(pats)
423 elif delete:
422 elif delete:
424 newinclude.difference_update(pats)
423 newinclude.difference_update(pats)
425 newexclude.difference_update(pats)
424 newexclude.difference_update(pats)
426
425
427 sparse.writeconfig(repo, newinclude, newexclude, newprofiles)
426 sparse.writeconfig(repo, newinclude, newexclude, newprofiles)
428
427
429 fcounts = map(
428 fcounts = map(
430 len,
429 len,
431 sparse.refreshwdir(repo, oldstatus, oldsparsematch,
430 sparse.refreshwdir(repo, oldstatus, oldsparsematch,
432 force=force))
431 force=force))
433
432
434 profilecount = (len(newprofiles - oldprofiles) -
433 profilecount = (len(newprofiles - oldprofiles) -
435 len(oldprofiles - newprofiles))
434 len(oldprofiles - newprofiles))
436 includecount = (len(newinclude - oldinclude) -
435 includecount = (len(newinclude - oldinclude) -
437 len(oldinclude - newinclude))
436 len(oldinclude - newinclude))
438 excludecount = (len(newexclude - oldexclude) -
437 excludecount = (len(newexclude - oldexclude) -
439 len(oldexclude - newexclude))
438 len(oldexclude - newexclude))
440 sparse.printchanges(ui, opts, profilecount, includecount,
439 sparse.printchanges(ui, opts, profilecount, includecount,
441 excludecount, *fcounts)
440 excludecount, *fcounts)
442 except Exception:
441 except Exception:
443 sparse.writeconfig(repo, oldinclude, oldexclude, oldprofiles)
442 sparse.writeconfig(repo, oldinclude, oldexclude, oldprofiles)
444 raise
443 raise
445 finally:
444 finally:
446 wlock.release()
445 wlock.release()
General Comments 0
You need to be logged in to leave comments. Login now