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