##// END OF EJS Templates
sparse: use with statement for wlock...
marmoute -
r52193:fa87eeeb default
parent child Browse files
Show More
@@ -1,392 +1,389 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 import (
76 from mercurial import (
77 cmdutil,
77 cmdutil,
78 commands,
78 commands,
79 error,
79 error,
80 extensions,
80 extensions,
81 logcmdutil,
81 logcmdutil,
82 merge as mergemod,
82 merge as mergemod,
83 pycompat,
83 pycompat,
84 registrar,
84 registrar,
85 sparse,
85 sparse,
86 util,
86 util,
87 )
87 )
88
88
89 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
89 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
90 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
90 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
91 # be specifying the version(s) of Mercurial they are tested with, or
91 # be specifying the version(s) of Mercurial they are tested with, or
92 # leave the attribute unspecified.
92 # leave the attribute unspecified.
93 testedwith = b'ships-with-hg-core'
93 testedwith = b'ships-with-hg-core'
94
94
95 cmdtable = {}
95 cmdtable = {}
96 command = registrar.command(cmdtable)
96 command = registrar.command(cmdtable)
97
97
98
98
99 def extsetup(ui):
99 def extsetup(ui):
100 sparse.enabled = True
100 sparse.enabled = True
101
101
102 _setupclone(ui)
102 _setupclone(ui)
103 _setuplog(ui)
103 _setuplog(ui)
104 _setupadd(ui)
104 _setupadd(ui)
105
105
106
106
107 def replacefilecache(cls, propname, replacement):
107 def replacefilecache(cls, propname, replacement):
108 """Replace a filecache property with a new class. This allows changing the
108 """Replace a filecache property with a new class. This allows changing the
109 cache invalidation condition."""
109 cache invalidation condition."""
110 origcls = cls
110 origcls = cls
111 assert callable(replacement)
111 assert callable(replacement)
112 while cls is not object:
112 while cls is not object:
113 if propname in cls.__dict__:
113 if propname in cls.__dict__:
114 orig = cls.__dict__[propname]
114 orig = cls.__dict__[propname]
115 setattr(cls, propname, replacement(orig))
115 setattr(cls, propname, replacement(orig))
116 break
116 break
117 cls = cls.__bases__[0]
117 cls = cls.__bases__[0]
118
118
119 if cls is object:
119 if cls is object:
120 raise AttributeError(
120 raise AttributeError(
121 _(b"type '%s' has no property '%s'") % (origcls, propname)
121 _(b"type '%s' has no property '%s'") % (origcls, propname)
122 )
122 )
123
123
124
124
125 def _setuplog(ui):
125 def _setuplog(ui):
126 entry = commands.table[b'log|history']
126 entry = commands.table[b'log|history']
127 entry[1].append(
127 entry[1].append(
128 (
128 (
129 b'',
129 b'',
130 b'sparse',
130 b'sparse',
131 None,
131 None,
132 b"limit to changesets affecting the sparse checkout",
132 b"limit to changesets affecting the sparse checkout",
133 )
133 )
134 )
134 )
135
135
136 def _initialrevs(orig, repo, wopts):
136 def _initialrevs(orig, repo, wopts):
137 revs = orig(repo, wopts)
137 revs = orig(repo, wopts)
138 if wopts.opts.get(b'sparse'):
138 if wopts.opts.get(b'sparse'):
139 sparsematch = sparse.matcher(repo)
139 sparsematch = sparse.matcher(repo)
140
140
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
144
145 revs = revs.filter(ctxmatch)
145 revs = revs.filter(ctxmatch)
146 return revs
146 return revs
147
147
148 extensions.wrapfunction(logcmdutil, '_initialrevs', _initialrevs)
148 extensions.wrapfunction(logcmdutil, '_initialrevs', _initialrevs)
149
149
150
150
151 def _clonesparsecmd(orig, ui, repo, *args, **opts):
151 def _clonesparsecmd(orig, ui, repo, *args, **opts):
152 include = opts.get('include')
152 include = opts.get('include')
153 exclude = opts.get('exclude')
153 exclude = opts.get('exclude')
154 enableprofile = opts.get('enable_profile')
154 enableprofile = opts.get('enable_profile')
155 narrow_pat = opts.get('narrow')
155 narrow_pat = opts.get('narrow')
156
156
157 # if --narrow is passed, it means they are includes and excludes for narrow
157 # if --narrow is passed, it means they are includes and excludes for narrow
158 # clone
158 # clone
159 if not narrow_pat and (include or exclude or enableprofile):
159 if not narrow_pat and (include or exclude or enableprofile):
160
160
161 def clonesparse(orig, ctx, *args, **kwargs):
161 def clonesparse(orig, ctx, *args, **kwargs):
162 sparse.updateconfig(
162 sparse.updateconfig(
163 ctx.repo().unfiltered(),
163 ctx.repo().unfiltered(),
164 {},
164 {},
165 include=include,
165 include=include,
166 exclude=exclude,
166 exclude=exclude,
167 enableprofile=enableprofile,
167 enableprofile=enableprofile,
168 usereporootpaths=True,
168 usereporootpaths=True,
169 )
169 )
170 return orig(ctx, *args, **kwargs)
170 return orig(ctx, *args, **kwargs)
171
171
172 extensions.wrapfunction(mergemod, 'update', clonesparse)
172 extensions.wrapfunction(mergemod, 'update', clonesparse)
173 return orig(ui, repo, *args, **opts)
173 return orig(ui, repo, *args, **opts)
174
174
175
175
176 def _setupclone(ui):
176 def _setupclone(ui):
177 entry = commands.table[b'clone']
177 entry = commands.table[b'clone']
178 entry[1].append((b'', b'enable-profile', [], b'enable a sparse profile'))
178 entry[1].append((b'', b'enable-profile', [], b'enable a sparse profile'))
179 entry[1].append((b'', b'include', [], b'include sparse pattern'))
179 entry[1].append((b'', b'include', [], b'include sparse pattern'))
180 entry[1].append((b'', b'exclude', [], b'exclude sparse pattern'))
180 entry[1].append((b'', b'exclude', [], b'exclude sparse pattern'))
181 extensions.wrapcommand(commands.table, b'clone', _clonesparsecmd)
181 extensions.wrapcommand(commands.table, b'clone', _clonesparsecmd)
182
182
183
183
184 def _setupadd(ui):
184 def _setupadd(ui):
185 entry = commands.table[b'add']
185 entry = commands.table[b'add']
186 entry[1].append(
186 entry[1].append(
187 (
187 (
188 b's',
188 b's',
189 b'sparse',
189 b'sparse',
190 None,
190 None,
191 b'also include directories of added files in sparse config',
191 b'also include directories of added files in sparse config',
192 )
192 )
193 )
193 )
194
194
195 def _add(orig, ui, repo, *pats, **opts):
195 def _add(orig, ui, repo, *pats, **opts):
196 if opts.get('sparse'):
196 if opts.get('sparse'):
197 dirs = set()
197 dirs = set()
198 for pat in pats:
198 for pat in pats:
199 dirname, basename = util.split(pat)
199 dirname, basename = util.split(pat)
200 dirs.add(dirname)
200 dirs.add(dirname)
201 sparse.updateconfig(repo, opts, include=list(dirs))
201 sparse.updateconfig(repo, opts, include=list(dirs))
202 return orig(ui, repo, *pats, **opts)
202 return orig(ui, repo, *pats, **opts)
203
203
204 extensions.wrapcommand(commands.table, b'add', _add)
204 extensions.wrapcommand(commands.table, b'add', _add)
205
205
206
206
207 @command(
207 @command(
208 b'debugsparse',
208 b'debugsparse',
209 [
209 [
210 (
210 (
211 b'I',
211 b'I',
212 b'include',
212 b'include',
213 [],
213 [],
214 _(b'include files in the sparse checkout'),
214 _(b'include files in the sparse checkout'),
215 _(b'PATTERN'),
215 _(b'PATTERN'),
216 ),
216 ),
217 (
217 (
218 b'X',
218 b'X',
219 b'exclude',
219 b'exclude',
220 [],
220 [],
221 _(b'exclude files in the sparse checkout'),
221 _(b'exclude files in the sparse checkout'),
222 _(b'PATTERN'),
222 _(b'PATTERN'),
223 ),
223 ),
224 (
224 (
225 b'd',
225 b'd',
226 b'delete',
226 b'delete',
227 [],
227 [],
228 _(b'delete an include/exclude rule'),
228 _(b'delete an include/exclude rule'),
229 _(b'PATTERN'),
229 _(b'PATTERN'),
230 ),
230 ),
231 (
231 (
232 b'f',
232 b'f',
233 b'force',
233 b'force',
234 False,
234 False,
235 _(b'allow changing rules even with pending changes'),
235 _(b'allow changing rules even with pending changes'),
236 ),
236 ),
237 (
237 (
238 b'',
238 b'',
239 b'enable-profile',
239 b'enable-profile',
240 [],
240 [],
241 _(b'enables the specified profile'),
241 _(b'enables the specified profile'),
242 _(b'PATTERN'),
242 _(b'PATTERN'),
243 ),
243 ),
244 (
244 (
245 b'',
245 b'',
246 b'disable-profile',
246 b'disable-profile',
247 [],
247 [],
248 _(b'disables the specified profile'),
248 _(b'disables the specified profile'),
249 _(b'PATTERN'),
249 _(b'PATTERN'),
250 ),
250 ),
251 (
251 (
252 b'',
252 b'',
253 b'import-rules',
253 b'import-rules',
254 [],
254 [],
255 _(b'imports rules from a file'),
255 _(b'imports rules from a file'),
256 _(b'PATTERN'),
256 _(b'PATTERN'),
257 ),
257 ),
258 (b'', b'clear-rules', False, _(b'clears local include/exclude rules')),
258 (b'', b'clear-rules', False, _(b'clears local include/exclude rules')),
259 (
259 (
260 b'',
260 b'',
261 b'refresh',
261 b'refresh',
262 False,
262 False,
263 _(b'updates the working after sparseness changes'),
263 _(b'updates the working after sparseness changes'),
264 ),
264 ),
265 (b'', b'reset', False, _(b'makes the repo full again')),
265 (b'', b'reset', False, _(b'makes the repo full again')),
266 ]
266 ]
267 + commands.templateopts,
267 + commands.templateopts,
268 _(b'[--OPTION]'),
268 _(b'[--OPTION]'),
269 helpbasic=True,
269 helpbasic=True,
270 )
270 )
271 def debugsparse(ui, repo, **opts):
271 def debugsparse(ui, repo, **opts):
272 """make the current checkout sparse, or edit the existing checkout
272 """make the current checkout sparse, or edit the existing checkout
273
273
274 The sparse command is used to make the current checkout sparse.
274 The sparse command is used to make the current checkout sparse.
275 This means files that don't meet the sparse condition will not be
275 This means files that don't meet the sparse condition will not be
276 written to disk, or show up in any working copy operations. It does
276 written to disk, or show up in any working copy operations. It does
277 not affect files in history in any way.
277 not affect files in history in any way.
278
278
279 Passing no arguments prints the currently applied sparse rules.
279 Passing no arguments prints the currently applied sparse rules.
280
280
281 --include and --exclude are used to add and remove files from the sparse
281 --include and --exclude are used to add and remove files from the sparse
282 checkout. The effects of adding an include or exclude rule are applied
282 checkout. The effects of adding an include or exclude rule are applied
283 immediately. If applying the new rule would cause a file with pending
283 immediately. If applying the new rule would cause a file with pending
284 changes to be added or removed, the command will fail. Pass --force to
284 changes to be added or removed, the command will fail. Pass --force to
285 force a rule change even with pending changes (the changes on disk will
285 force a rule change even with pending changes (the changes on disk will
286 be preserved).
286 be preserved).
287
287
288 --delete removes an existing include/exclude rule. The effects are
288 --delete removes an existing include/exclude rule. The effects are
289 immediate.
289 immediate.
290
290
291 --refresh refreshes the files on disk based on the sparse rules. This is
291 --refresh refreshes the files on disk based on the sparse rules. This is
292 only necessary if .hg/sparse was changed by hand.
292 only necessary if .hg/sparse was changed by hand.
293
293
294 --enable-profile and --disable-profile accept a path to a .hgsparse file.
294 --enable-profile and --disable-profile accept a path to a .hgsparse file.
295 This allows defining sparse checkouts and tracking them inside the
295 This allows defining sparse checkouts and tracking them inside the
296 repository. This is useful for defining commonly used sparse checkouts for
296 repository. This is useful for defining commonly used sparse checkouts for
297 many people to use. As the profile definition changes over time, the sparse
297 many people to use. As the profile definition changes over time, the sparse
298 checkout will automatically be updated appropriately, depending on which
298 checkout will automatically be updated appropriately, depending on which
299 changeset is checked out. Changes to .hgsparse are not applied until they
299 changeset is checked out. Changes to .hgsparse are not applied until they
300 have been committed.
300 have been committed.
301
301
302 --import-rules accepts a path to a file containing rules in the .hgsparse
302 --import-rules accepts a path to a file containing rules in the .hgsparse
303 format, allowing you to add --include, --exclude and --enable-profile rules
303 format, allowing you to add --include, --exclude and --enable-profile rules
304 in bulk. Like the --include, --exclude and --enable-profile switches, the
304 in bulk. Like the --include, --exclude and --enable-profile switches, the
305 changes are applied immediately.
305 changes are applied immediately.
306
306
307 --clear-rules removes all local include and exclude rules, while leaving
307 --clear-rules removes all local include and exclude rules, while leaving
308 any enabled profiles in place.
308 any enabled profiles in place.
309
309
310 Returns 0 if editing the sparse checkout succeeds.
310 Returns 0 if editing the sparse checkout succeeds.
311 """
311 """
312 opts = pycompat.byteskwargs(opts)
312 opts = pycompat.byteskwargs(opts)
313 include = opts.get(b'include')
313 include = opts.get(b'include')
314 exclude = opts.get(b'exclude')
314 exclude = opts.get(b'exclude')
315 force = opts.get(b'force')
315 force = opts.get(b'force')
316 enableprofile = opts.get(b'enable_profile')
316 enableprofile = opts.get(b'enable_profile')
317 disableprofile = opts.get(b'disable_profile')
317 disableprofile = opts.get(b'disable_profile')
318 importrules = opts.get(b'import_rules')
318 importrules = opts.get(b'import_rules')
319 clearrules = opts.get(b'clear_rules')
319 clearrules = opts.get(b'clear_rules')
320 delete = opts.get(b'delete')
320 delete = opts.get(b'delete')
321 refresh = opts.get(b'refresh')
321 refresh = opts.get(b'refresh')
322 reset = opts.get(b'reset')
322 reset = opts.get(b'reset')
323 action = cmdutil.check_at_most_one_arg(
323 action = cmdutil.check_at_most_one_arg(
324 opts, b'import_rules', b'clear_rules', b'refresh'
324 opts, b'import_rules', b'clear_rules', b'refresh'
325 )
325 )
326 updateconfig = bool(
326 updateconfig = bool(
327 include or exclude or delete or reset or enableprofile or disableprofile
327 include or exclude or delete or reset or enableprofile or disableprofile
328 )
328 )
329 count = sum([updateconfig, bool(action)])
329 count = sum([updateconfig, bool(action)])
330 if count > 1:
330 if count > 1:
331 raise error.Abort(_(b"too many flags specified"))
331 raise error.Abort(_(b"too many flags specified"))
332
332
333 # enable sparse on repo even if the requirements is missing.
333 # enable sparse on repo even if the requirements is missing.
334 repo._has_sparse = True
334 repo._has_sparse = True
335
335
336 if count == 0:
336 if count == 0:
337 if repo.vfs.exists(b'sparse'):
337 if repo.vfs.exists(b'sparse'):
338 ui.status(repo.vfs.read(b"sparse") + b"\n")
338 ui.status(repo.vfs.read(b"sparse") + b"\n")
339 temporaryincludes = sparse.readtemporaryincludes(repo)
339 temporaryincludes = sparse.readtemporaryincludes(repo)
340 if temporaryincludes:
340 if temporaryincludes:
341 ui.status(
341 ui.status(
342 _(b"Temporarily Included Files (for merge/rebase):\n")
342 _(b"Temporarily Included Files (for merge/rebase):\n")
343 )
343 )
344 ui.status((b"\n".join(temporaryincludes) + b"\n"))
344 ui.status((b"\n".join(temporaryincludes) + b"\n"))
345 return
345 return
346 else:
346 else:
347 raise error.Abort(
347 raise error.Abort(
348 _(
348 _(
349 b'the debugsparse command is only supported on'
349 b'the debugsparse command is only supported on'
350 b' sparse repositories'
350 b' sparse repositories'
351 )
351 )
352 )
352 )
353
353
354 if updateconfig:
354 if updateconfig:
355 sparse.updateconfig(
355 sparse.updateconfig(
356 repo,
356 repo,
357 opts,
357 opts,
358 include=include,
358 include=include,
359 exclude=exclude,
359 exclude=exclude,
360 reset=reset,
360 reset=reset,
361 delete=delete,
361 delete=delete,
362 enableprofile=enableprofile,
362 enableprofile=enableprofile,
363 disableprofile=disableprofile,
363 disableprofile=disableprofile,
364 force=force,
364 force=force,
365 )
365 )
366
366
367 if importrules:
367 if importrules:
368 sparse.importfromfiles(repo, opts, importrules, force=force)
368 sparse.importfromfiles(repo, opts, importrules, force=force)
369
369
370 if clearrules:
370 if clearrules:
371 sparse.clearrules(repo, force=force)
371 sparse.clearrules(repo, force=force)
372
372
373 if refresh:
373 if refresh:
374 try:
374 with repo.wlock():
375 wlock = repo.wlock()
376 fcounts = pycompat.maplist(
375 fcounts = pycompat.maplist(
377 len,
376 len,
378 sparse.refreshwdir(
377 sparse.refreshwdir(
379 repo, repo.status(), sparse.matcher(repo), force=force
378 repo, repo.status(), sparse.matcher(repo), force=force
380 ),
379 ),
381 )
380 )
382 sparse.printchanges(
381 sparse.printchanges(
383 ui,
382 ui,
384 opts,
383 opts,
385 added=fcounts[0],
384 added=fcounts[0],
386 dropped=fcounts[1],
385 dropped=fcounts[1],
387 conflicting=fcounts[2],
386 conflicting=fcounts[2],
388 )
387 )
389 finally:
390 wlock.release()
391
388
392 del repo._has_sparse
389 del repo._has_sparse
General Comments 0
You need to be logged in to leave comments. Login now