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