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