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