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