##// END OF EJS Templates
narrow: drop explicit dirstate write...
Martin von Zweigbergk -
r41212:4475322b default
parent child Browse files
Show More
@@ -1,481 +1,481
1 1 # narrowcommands.py - command modifications for narrowhg extension
2 2 #
3 3 # Copyright 2017 Google, 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 from __future__ import absolute_import
8 8
9 9 import itertools
10 10 import os
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 bundle2,
15 15 cmdutil,
16 16 commands,
17 17 discovery,
18 18 encoding,
19 19 error,
20 20 exchange,
21 21 extensions,
22 22 hg,
23 23 merge,
24 24 narrowspec,
25 25 node,
26 26 pycompat,
27 27 registrar,
28 28 repair,
29 29 repository,
30 30 repoview,
31 31 sparse,
32 32 util,
33 33 wireprototypes,
34 34 )
35 35
36 36 table = {}
37 37 command = registrar.command(table)
38 38
39 39 def setup():
40 40 """Wraps user-facing mercurial commands with narrow-aware versions."""
41 41
42 42 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
43 43 entry[1].append(('', 'narrow', None,
44 44 _("create a narrow clone of select files")))
45 45 entry[1].append(('', 'depth', '',
46 46 _("limit the history fetched by distance from heads")))
47 47 entry[1].append(('', 'narrowspec', '',
48 48 _("read narrowspecs from file")))
49 49 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
50 50 if 'sparse' not in extensions.enabled():
51 51 entry[1].append(('', 'include', [],
52 52 _("specifically fetch this file/directory")))
53 53 entry[1].append(
54 54 ('', 'exclude', [],
55 55 _("do not fetch this file/directory, even if included")))
56 56
57 57 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
58 58 entry[1].append(('', 'depth', '',
59 59 _("limit the history fetched by distance from heads")))
60 60
61 61 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
62 62
63 63 def clonenarrowcmd(orig, ui, repo, *args, **opts):
64 64 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
65 65 opts = pycompat.byteskwargs(opts)
66 66 wrappedextraprepare = util.nullcontextmanager()
67 67 narrowspecfile = opts['narrowspec']
68 68
69 69 if narrowspecfile:
70 70 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
71 71 ui.status(_("reading narrowspec from '%s'\n") % filepath)
72 72 try:
73 73 fdata = util.readfile(filepath)
74 74 except IOError as inst:
75 75 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
76 76 (filepath, encoding.strtolocal(inst.strerror)))
77 77
78 78 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
79 79 if profiles:
80 80 raise error.Abort(_("cannot specify other files using '%include' in"
81 81 " narrowspec"))
82 82
83 83 narrowspec.validatepatterns(includes)
84 84 narrowspec.validatepatterns(excludes)
85 85
86 86 # narrowspec is passed so we should assume that user wants narrow clone
87 87 opts['narrow'] = True
88 88 opts['include'].extend(includes)
89 89 opts['exclude'].extend(excludes)
90 90
91 91 if opts['narrow']:
92 92 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
93 93 orig(pullop, kwargs)
94 94
95 95 if opts.get('depth'):
96 96 kwargs['depth'] = opts['depth']
97 97 wrappedextraprepare = extensions.wrappedfunction(exchange,
98 98 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
99 99
100 100 with wrappedextraprepare:
101 101 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
102 102
103 103 def pullnarrowcmd(orig, ui, repo, *args, **opts):
104 104 """Wraps pull command to allow modifying narrow spec."""
105 105 wrappedextraprepare = util.nullcontextmanager()
106 106 if repository.NARROW_REQUIREMENT in repo.requirements:
107 107
108 108 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
109 109 orig(pullop, kwargs)
110 110 if opts.get(r'depth'):
111 111 kwargs['depth'] = opts[r'depth']
112 112 wrappedextraprepare = extensions.wrappedfunction(exchange,
113 113 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
114 114
115 115 with wrappedextraprepare:
116 116 return orig(ui, repo, *args, **opts)
117 117
118 118 def archivenarrowcmd(orig, ui, repo, *args, **opts):
119 119 """Wraps archive command to narrow the default includes."""
120 120 if repository.NARROW_REQUIREMENT in repo.requirements:
121 121 repo_includes, repo_excludes = repo.narrowpats
122 122 includes = set(opts.get(r'include', []))
123 123 excludes = set(opts.get(r'exclude', []))
124 124 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
125 125 includes, excludes, repo_includes, repo_excludes)
126 126 if includes:
127 127 opts[r'include'] = includes
128 128 if excludes:
129 129 opts[r'exclude'] = excludes
130 130 return orig(ui, repo, *args, **opts)
131 131
132 132 def pullbundle2extraprepare(orig, pullop, kwargs):
133 133 repo = pullop.repo
134 134 if repository.NARROW_REQUIREMENT not in repo.requirements:
135 135 return orig(pullop, kwargs)
136 136
137 137 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
138 138 raise error.Abort(_("server does not support narrow clones"))
139 139 orig(pullop, kwargs)
140 140 kwargs['narrow'] = True
141 141 include, exclude = repo.narrowpats
142 142 kwargs['oldincludepats'] = include
143 143 kwargs['oldexcludepats'] = exclude
144 144 if include:
145 145 kwargs['includepats'] = include
146 146 if exclude:
147 147 kwargs['excludepats'] = exclude
148 148 # calculate known nodes only in ellipses cases because in non-ellipses cases
149 149 # we have all the nodes
150 150 if wireprototypes.ELLIPSESCAP in pullop.remote.capabilities():
151 151 kwargs['known'] = [node.hex(ctx.node()) for ctx in
152 152 repo.set('::%ln', pullop.common)
153 153 if ctx.node() != node.nullid]
154 154 if not kwargs['known']:
155 155 # Mercurial serializes an empty list as '' and deserializes it as
156 156 # [''], so delete it instead to avoid handling the empty string on
157 157 # the server.
158 158 del kwargs['known']
159 159
160 160 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
161 161 pullbundle2extraprepare)
162 162
163 163 # This is an extension point for filesystems that need to do something other
164 164 # than just blindly unlink the files. It's not clear what arguments would be
165 165 # useful, so we're passing in a fair number of them, some of them redundant.
166 166 def _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes, newexcludes,
167 167 oldmatch, newmatch):
168 168 for f in repo.dirstate:
169 169 if not newmatch(f):
170 170 repo.dirstate.drop(f)
171 171 repo.wvfs.unlinkpath(f)
172 172
173 173 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
174 174 newincludes, newexcludes, force):
175 175 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
176 176 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
177 177
178 178 # This is essentially doing "hg outgoing" to find all local-only
179 179 # commits. We will then check that the local-only commits don't
180 180 # have any changes to files that will be untracked.
181 181 unfi = repo.unfiltered()
182 182 outgoing = discovery.findcommonoutgoing(unfi, remote,
183 183 commoninc=commoninc)
184 184 ui.status(_('looking for local changes to affected paths\n'))
185 185 localnodes = []
186 186 for n in itertools.chain(outgoing.missing, outgoing.excluded):
187 187 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
188 188 localnodes.append(n)
189 189 revstostrip = unfi.revs('descendants(%ln)', localnodes)
190 190 hiddenrevs = repoview.filterrevs(repo, 'visible')
191 191 visibletostrip = list(repo.changelog.node(r)
192 192 for r in (revstostrip - hiddenrevs))
193 193 if visibletostrip:
194 194 ui.status(_('The following changeset(s) or their ancestors have '
195 195 'local changes not on the remote:\n'))
196 196 maxnodes = 10
197 197 if ui.verbose or len(visibletostrip) <= maxnodes:
198 198 for n in visibletostrip:
199 199 ui.status('%s\n' % node.short(n))
200 200 else:
201 201 for n in visibletostrip[:maxnodes]:
202 202 ui.status('%s\n' % node.short(n))
203 203 ui.status(_('...and %d more, use --verbose to list all\n') %
204 204 (len(visibletostrip) - maxnodes))
205 205 if not force:
206 206 raise error.Abort(_('local changes found'),
207 207 hint=_('use --force-delete-local-changes to '
208 208 'ignore'))
209 209
210 210 with ui.uninterruptible():
211 211 if revstostrip:
212 212 tostrip = [unfi.changelog.node(r) for r in revstostrip]
213 213 if repo['.'].node() in tostrip:
214 214 # stripping working copy, so move to a different commit first
215 215 urev = max(repo.revs('(::%n) - %ln + null',
216 216 repo['.'].node(), visibletostrip))
217 217 hg.clean(repo, urev)
218 218 overrides = {('devel', 'strip-obsmarkers'): False}
219 219 with ui.configoverride(overrides, 'narrow'):
220 220 repair.strip(ui, unfi, tostrip, topic='narrow')
221 221
222 222 todelete = []
223 223 for f, f2, size in repo.store.datafiles():
224 224 if f.startswith('data/'):
225 225 file = f[5:-2]
226 226 if not newmatch(file):
227 227 todelete.append(f)
228 228 elif f.startswith('meta/'):
229 229 dir = f[5:-13]
230 230 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
231 231 include = True
232 232 for d in dirs:
233 233 visit = newmatch.visitdir(d)
234 234 if not visit:
235 235 include = False
236 236 break
237 237 if visit == 'all':
238 238 break
239 239 if not include:
240 240 todelete.append(f)
241 241
242 242 repo.destroying()
243 243
244 244 with repo.transaction("narrowing"):
245 245 # Update narrowspec before removing revlogs, so repo won't be
246 246 # corrupt in case of crash
247 247 repo.setnarrowpats(newincludes, newexcludes)
248 248
249 249 for f in todelete:
250 250 ui.status(_('deleting %s\n') % f)
251 251 util.unlinkpath(repo.svfs.join(f))
252 252 repo.store.markremoved(f)
253 253
254 254 _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes,
255 255 newexcludes, oldmatch, newmatch)
256 256
257 257 repo.destroyed()
258 258
259 259 def _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
260 260 newincludes, newexcludes):
261 261 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
262 262
263 263 # for now we assume that if a server has ellipses enabled, we will be
264 264 # exchanging ellipses nodes. In future we should add ellipses as a client
265 265 # side requirement (maybe) to distinguish a client is shallow or not and
266 266 # then send that information to server whether we want ellipses or not.
267 267 # Theoretically a non-ellipses repo should be able to use narrow
268 268 # functionality from an ellipses enabled server
269 269 ellipsesremote = wireprototypes.ELLIPSESCAP in remote.capabilities()
270 270
271 271 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
272 272 orig(pullop, kwargs)
273 273 # The old{in,ex}cludepats have already been set by orig()
274 274 kwargs['includepats'] = newincludes
275 275 kwargs['excludepats'] = newexcludes
276 276 wrappedextraprepare = extensions.wrappedfunction(exchange,
277 277 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
278 278
279 279 # define a function that narrowbundle2 can call after creating the
280 280 # backup bundle, but before applying the bundle from the server
281 281 def setnewnarrowpats():
282 282 repo.setnarrowpats(newincludes, newexcludes)
283 283 repo.setnewnarrowpats = setnewnarrowpats
284 284 # silence the devel-warning of applying an empty changegroup
285 285 overrides = {('devel', 'all-warnings'): False}
286 286
287 287 with ui.uninterruptible():
288 288 common = commoninc[0]
289 289 if ellipsesremote:
290 290 ds = repo.dirstate
291 291 p1, p2 = ds.p1(), ds.p2()
292 292 with ds.parentchange():
293 293 ds.setparents(node.nullid, node.nullid)
294 294 with wrappedextraprepare,\
295 295 repo.ui.configoverride(overrides, 'widen'):
296 296 exchange.pull(repo, remote, heads=common)
297 297 with ds.parentchange():
298 298 ds.setparents(p1, p2)
299 299 else:
300 300 with remote.commandexecutor() as e:
301 301 bundle = e.callcommand('narrow_widen', {
302 302 'oldincludes': oldincludes,
303 303 'oldexcludes': oldexcludes,
304 304 'newincludes': newincludes,
305 305 'newexcludes': newexcludes,
306 306 'cgversion': '03',
307 307 'commonheads': common,
308 308 'known': [],
309 309 'ellipses': False,
310 310 }).result()
311 311
312 312 with repo.transaction('widening') as tr,\
313 313 repo.ui.configoverride(overrides, 'widen'):
314 314 tgetter = lambda: tr
315 315 bundle2.processbundle(repo, bundle,
316 316 transactiongetter=tgetter)
317 317
318 318 repo.setnewnarrowpats()
319 319 actions = merge.emptyactions()
320 320 addgaction = actions['g'].append
321 321
322 322 mf = repo['.'].manifest().matches(newmatch)
323 323 for f, fn in mf.iteritems():
324 324 if f not in repo.dirstate:
325 325 addgaction((f, (mf.flags(f), False),
326 326 "add from widened narrow clone"))
327 327
328 328 merge.applyupdates(repo, actions, wctx=repo[None],
329 329 mctx=repo['.'], overwrite=False)
330 330 merge.recordupdates(repo, actions, branchmerge=False)
331 331
332 332 # TODO(rdamazio): Make new matcher format and update description
333 333 @command('tracked',
334 334 [('', 'addinclude', [], _('new paths to include')),
335 335 ('', 'removeinclude', [], _('old paths to no longer include')),
336 336 ('', 'addexclude', [], _('new paths to exclude')),
337 337 ('', 'import-rules', '', _('import narrowspecs from a file')),
338 338 ('', 'removeexclude', [], _('old paths to no longer exclude')),
339 339 ('', 'clear', False, _('whether to replace the existing narrowspec')),
340 340 ('', 'force-delete-local-changes', False,
341 341 _('forces deletion of local changes when narrowing')),
342 342 ('', 'update-working-copy', False,
343 343 _('update working copy when the store has changed')),
344 344 ] + commands.remoteopts,
345 345 _('[OPTIONS]... [REMOTE]'),
346 346 inferrepo=True)
347 347 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
348 348 """show or change the current narrowspec
349 349
350 350 With no argument, shows the current narrowspec entries, one per line. Each
351 351 line will be prefixed with 'I' or 'X' for included or excluded patterns,
352 352 respectively.
353 353
354 354 The narrowspec is comprised of expressions to match remote files and/or
355 355 directories that should be pulled into your client.
356 356 The narrowspec has *include* and *exclude* expressions, with excludes always
357 357 trumping includes: that is, if a file matches an exclude expression, it will
358 358 be excluded even if it also matches an include expression.
359 359 Excluding files that were never included has no effect.
360 360
361 361 Each included or excluded entry is in the format described by
362 362 'hg help patterns'.
363 363
364 364 The options allow you to add or remove included and excluded expressions.
365 365
366 366 If --clear is specified, then all previous includes and excludes are DROPPED
367 367 and replaced by the new ones specified to --addinclude and --addexclude.
368 368 If --clear is specified without any further options, the narrowspec will be
369 369 empty and will not match any files.
370 370 """
371 371 opts = pycompat.byteskwargs(opts)
372 372 if repository.NARROW_REQUIREMENT not in repo.requirements:
373 373 raise error.Abort(_('the narrow command is only supported on '
374 374 'respositories cloned with --narrow'))
375 375
376 376 # Before supporting, decide whether it "hg tracked --clear" should mean
377 377 # tracking no paths or all paths.
378 378 if opts['clear']:
379 379 raise error.Abort(_('the --clear option is not yet supported'))
380 380
381 381 # import rules from a file
382 382 newrules = opts.get('import_rules')
383 383 if newrules:
384 384 try:
385 385 filepath = os.path.join(encoding.getcwd(), newrules)
386 386 fdata = util.readfile(filepath)
387 387 except IOError as inst:
388 388 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
389 389 (filepath, encoding.strtolocal(inst.strerror)))
390 390 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
391 391 'narrow')
392 392 if profiles:
393 393 raise error.Abort(_("including other spec files using '%include' "
394 394 "is not supported in narrowspec"))
395 395 opts['addinclude'].extend(includepats)
396 396 opts['addexclude'].extend(excludepats)
397 397
398 398 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
399 399 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
400 400 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
401 401 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
402 402
403 403 update_working_copy = opts['update_working_copy']
404 404 only_show = not (addedincludes or removedincludes or addedexcludes or
405 405 removedexcludes or newrules or update_working_copy)
406 406
407 407 oldincludes, oldexcludes = repo.narrowpats
408 408
409 409 # filter the user passed additions and deletions into actual additions and
410 410 # deletions of excludes and includes
411 411 addedincludes -= oldincludes
412 412 removedincludes &= oldincludes
413 413 addedexcludes -= oldexcludes
414 414 removedexcludes &= oldexcludes
415 415
416 416 widening = addedincludes or removedexcludes
417 417 narrowing = removedincludes or addedexcludes
418 418
419 419 # Only print the current narrowspec.
420 420 if only_show:
421 421 ui.pager('tracked')
422 422 fm = ui.formatter('narrow', opts)
423 423 for i in sorted(oldincludes):
424 424 fm.startitem()
425 425 fm.write('status', '%s ', 'I', label='narrow.included')
426 426 fm.write('pat', '%s\n', i, label='narrow.included')
427 427 for i in sorted(oldexcludes):
428 428 fm.startitem()
429 429 fm.write('status', '%s ', 'X', label='narrow.excluded')
430 430 fm.write('pat', '%s\n', i, label='narrow.excluded')
431 431 fm.end()
432 432 return 0
433 433
434 434 if update_working_copy:
435 435 with repo.wlock(), repo.lock(), repo.transaction('narrow-wc') as tr:
436 narrowspec.updateworkingcopy(repo, tr)
436 narrowspec.updateworkingcopy(repo)
437 437 narrowspec.copytoworkingcopy(repo, tr)
438 438 return 0
439 439
440 440 if not widening and not narrowing:
441 441 ui.status(_("nothing to widen or narrow\n"))
442 442 return 0
443 443
444 444 with repo.wlock(), repo.lock():
445 445 cmdutil.bailifchanged(repo)
446 446
447 447 # Find the revisions we have in common with the remote. These will
448 448 # be used for finding local-only changes for narrowing. They will
449 449 # also define the set of revisions to update for widening.
450 450 remotepath = ui.expandpath(remotepath or 'default')
451 451 url, branches = hg.parseurl(remotepath)
452 452 ui.status(_('comparing with %s\n') % util.hidepassword(url))
453 453 remote = hg.peer(repo, opts, url)
454 454
455 455 # check narrow support before doing anything if widening needs to be
456 456 # performed. In future we should also abort if client is ellipses and
457 457 # server does not support ellipses
458 458 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
459 459 raise error.Abort(_("server does not support narrow clones"))
460 460
461 461 commoninc = discovery.findcommonincoming(repo, remote)
462 462
463 463 if narrowing:
464 464 newincludes = oldincludes - removedincludes
465 465 newexcludes = oldexcludes | addedexcludes
466 466 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
467 467 newincludes, newexcludes,
468 468 opts['force_delete_local_changes'])
469 469 # _narrow() updated the narrowspec and _widen() below needs to
470 470 # use the updated values as its base (otherwise removed includes
471 471 # and addedexcludes will be lost in the resulting narrowspec)
472 472 oldincludes = newincludes
473 473 oldexcludes = newexcludes
474 474
475 475 if widening:
476 476 newincludes = oldincludes | addedincludes
477 477 newexcludes = oldexcludes - removedexcludes
478 478 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
479 479 newincludes, newexcludes)
480 480
481 481 return 0
@@ -1,298 +1,296
1 1 # narrowspec.py - methods for working with a narrow view of a repository
2 2 #
3 3 # Copyright 2017 Google, 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 from __future__ import absolute_import
9 9
10 10 import errno
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 error,
15 15 match as matchmod,
16 16 merge,
17 17 repository,
18 18 sparse,
19 19 util,
20 20 )
21 21
22 22 # The file in .hg/store/ that indicates which paths exit in the store
23 23 FILENAME = 'narrowspec'
24 24 # The file in .hg/ that indicates which paths exit in the dirstate
25 25 DIRSTATE_FILENAME = 'narrowspec.dirstate'
26 26
27 27 # Pattern prefixes that are allowed in narrow patterns. This list MUST
28 28 # only contain patterns that are fast and safe to evaluate. Keep in mind
29 29 # that patterns are supplied by clients and executed on remote servers
30 30 # as part of wire protocol commands. That means that changes to this
31 31 # data structure influence the wire protocol and should not be taken
32 32 # lightly - especially removals.
33 33 VALID_PREFIXES = (
34 34 b'path:',
35 35 b'rootfilesin:',
36 36 )
37 37
38 38 def normalizesplitpattern(kind, pat):
39 39 """Returns the normalized version of a pattern and kind.
40 40
41 41 Returns a tuple with the normalized kind and normalized pattern.
42 42 """
43 43 pat = pat.rstrip('/')
44 44 _validatepattern(pat)
45 45 return kind, pat
46 46
47 47 def _numlines(s):
48 48 """Returns the number of lines in s, including ending empty lines."""
49 49 # We use splitlines because it is Unicode-friendly and thus Python 3
50 50 # compatible. However, it does not count empty lines at the end, so trick
51 51 # it by adding a character at the end.
52 52 return len((s + 'x').splitlines())
53 53
54 54 def _validatepattern(pat):
55 55 """Validates the pattern and aborts if it is invalid.
56 56
57 57 Patterns are stored in the narrowspec as newline-separated
58 58 POSIX-style bytestring paths. There's no escaping.
59 59 """
60 60
61 61 # We use newlines as separators in the narrowspec file, so don't allow them
62 62 # in patterns.
63 63 if _numlines(pat) > 1:
64 64 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
65 65
66 66 components = pat.split('/')
67 67 if '.' in components or '..' in components:
68 68 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
69 69
70 70 def normalizepattern(pattern, defaultkind='path'):
71 71 """Returns the normalized version of a text-format pattern.
72 72
73 73 If the pattern has no kind, the default will be added.
74 74 """
75 75 kind, pat = matchmod._patsplit(pattern, defaultkind)
76 76 return '%s:%s' % normalizesplitpattern(kind, pat)
77 77
78 78 def parsepatterns(pats):
79 79 """Parses an iterable of patterns into a typed pattern set.
80 80
81 81 Patterns are assumed to be ``path:`` if no prefix is present.
82 82 For safety and performance reasons, only some prefixes are allowed.
83 83 See ``validatepatterns()``.
84 84
85 85 This function should be used on patterns that come from the user to
86 86 normalize and validate them to the internal data structure used for
87 87 representing patterns.
88 88 """
89 89 res = {normalizepattern(orig) for orig in pats}
90 90 validatepatterns(res)
91 91 return res
92 92
93 93 def validatepatterns(pats):
94 94 """Validate that patterns are in the expected data structure and format.
95 95
96 96 And that is a set of normalized patterns beginning with ``path:`` or
97 97 ``rootfilesin:``.
98 98
99 99 This function should be used to validate internal data structures
100 100 and patterns that are loaded from sources that use the internal,
101 101 prefixed pattern representation (but can't necessarily be fully trusted).
102 102 """
103 103 if not isinstance(pats, set):
104 104 raise error.ProgrammingError('narrow patterns should be a set; '
105 105 'got %r' % pats)
106 106
107 107 for pat in pats:
108 108 if not pat.startswith(VALID_PREFIXES):
109 109 # Use a Mercurial exception because this can happen due to user
110 110 # bugs (e.g. manually updating spec file).
111 111 raise error.Abort(_('invalid prefix on narrow pattern: %s') % pat,
112 112 hint=_('narrow patterns must begin with one of '
113 113 'the following: %s') %
114 114 ', '.join(VALID_PREFIXES))
115 115
116 116 def format(includes, excludes):
117 117 output = '[include]\n'
118 118 for i in sorted(includes - excludes):
119 119 output += i + '\n'
120 120 output += '[exclude]\n'
121 121 for e in sorted(excludes):
122 122 output += e + '\n'
123 123 return output
124 124
125 125 def match(root, include=None, exclude=None):
126 126 if not include:
127 127 # Passing empty include and empty exclude to matchmod.match()
128 128 # gives a matcher that matches everything, so explicitly use
129 129 # the nevermatcher.
130 130 return matchmod.never(root, '')
131 131 return matchmod.match(root, '', [], include=include or [],
132 132 exclude=exclude or [])
133 133
134 134 def parseconfig(ui, spec):
135 135 # maybe we should care about the profiles returned too
136 136 includepats, excludepats, profiles = sparse.parseconfig(ui, spec, 'narrow')
137 137 if profiles:
138 138 raise error.Abort(_("including other spec files using '%include' is not"
139 139 " supported in narrowspec"))
140 140
141 141 validatepatterns(includepats)
142 142 validatepatterns(excludepats)
143 143
144 144 return includepats, excludepats
145 145
146 146 def load(repo):
147 147 try:
148 148 spec = repo.svfs.read(FILENAME)
149 149 except IOError as e:
150 150 # Treat "narrowspec does not exist" the same as "narrowspec file exists
151 151 # and is empty".
152 152 if e.errno == errno.ENOENT:
153 153 return set(), set()
154 154 raise
155 155
156 156 return parseconfig(repo.ui, spec)
157 157
158 158 def save(repo, includepats, excludepats):
159 159 validatepatterns(includepats)
160 160 validatepatterns(excludepats)
161 161 spec = format(includepats, excludepats)
162 162 repo.svfs.write(FILENAME, spec)
163 163
164 164 def copytoworkingcopy(repo, tr):
165 165 if tr:
166 166 def write(file):
167 167 spec = repo.svfs.read(FILENAME)
168 168 file.write(spec)
169 169 file.close()
170 170 tr.addfilegenerator('narrowspec', (DIRSTATE_FILENAME,), write,
171 171 location='plain')
172 172 else:
173 173 spec = repo.svfs.read(FILENAME)
174 174 repo.vfs.write(DIRSTATE_FILENAME, spec)
175 175
176 176 def savebackup(repo, backupname):
177 177 if repository.NARROW_REQUIREMENT not in repo.requirements:
178 178 return
179 179 svfs = repo.svfs
180 180 svfs.tryunlink(backupname)
181 181 util.copyfile(svfs.join(FILENAME), svfs.join(backupname), hardlink=True)
182 182
183 183 def restorebackup(repo, backupname):
184 184 if repository.NARROW_REQUIREMENT not in repo.requirements:
185 185 return
186 186 util.rename(repo.svfs.join(backupname), repo.svfs.join(FILENAME))
187 187
188 188 def clearbackup(repo, backupname):
189 189 if repository.NARROW_REQUIREMENT not in repo.requirements:
190 190 return
191 191 repo.svfs.unlink(backupname)
192 192
193 193 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
194 194 r""" Restricts the patterns according to repo settings,
195 195 results in a logical AND operation
196 196
197 197 :param req_includes: requested includes
198 198 :param req_excludes: requested excludes
199 199 :param repo_includes: repo includes
200 200 :param repo_excludes: repo excludes
201 201 :return: include patterns, exclude patterns, and invalid include patterns.
202 202
203 203 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
204 204 (set(['f1']), {}, [])
205 205 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
206 206 (set(['f1']), {}, [])
207 207 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
208 208 (set(['f1/fc1']), {}, [])
209 209 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
210 210 ([], set(['path:.']), [])
211 211 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
212 212 (set(['f2/fc2']), {}, [])
213 213 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
214 214 ([], set(['path:.']), [])
215 215 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
216 216 (set(['f1/$non_exitent_var']), {}, [])
217 217 """
218 218 res_excludes = set(req_excludes)
219 219 res_excludes.update(repo_excludes)
220 220 invalid_includes = []
221 221 if not req_includes:
222 222 res_includes = set(repo_includes)
223 223 elif 'path:.' not in repo_includes:
224 224 res_includes = []
225 225 for req_include in req_includes:
226 226 req_include = util.expandpath(util.normpath(req_include))
227 227 if req_include in repo_includes:
228 228 res_includes.append(req_include)
229 229 continue
230 230 valid = False
231 231 for repo_include in repo_includes:
232 232 if req_include.startswith(repo_include + '/'):
233 233 valid = True
234 234 res_includes.append(req_include)
235 235 break
236 236 if not valid:
237 237 invalid_includes.append(req_include)
238 238 if len(res_includes) == 0:
239 239 res_excludes = {'path:.'}
240 240 else:
241 241 res_includes = set(res_includes)
242 242 else:
243 243 res_includes = set(req_includes)
244 244 return res_includes, res_excludes, invalid_includes
245 245
246 246 # These two are extracted for extensions (specifically for Google's CitC file
247 247 # system)
248 248 def _deletecleanfiles(repo, files):
249 249 for f in files:
250 250 repo.wvfs.unlinkpath(f)
251 251
252 252 def _writeaddedfiles(repo, pctx, files):
253 253 actions = merge.emptyactions()
254 254 addgaction = actions['g'].append
255 255 mf = repo['.'].manifest()
256 256 for f in files:
257 257 if not repo.wvfs.exists(f):
258 258 addgaction((f, (mf.flags(f), False), "narrowspec updated"))
259 259 merge.applyupdates(repo, actions, wctx=repo[None],
260 260 mctx=repo['.'], overwrite=False)
261 261
262 262 def checkworkingcopynarrowspec(repo):
263 263 storespec = repo.svfs.tryread(FILENAME)
264 264 wcspec = repo.vfs.tryread(DIRSTATE_FILENAME)
265 265 if wcspec != storespec:
266 266 raise error.Abort(_("working copy's narrowspec is stale"),
267 267 hint=_("run 'hg tracked --update-working-copy'"))
268 268
269 def updateworkingcopy(repo, tr):
269 def updateworkingcopy(repo):
270 270 oldspec = repo.vfs.tryread(DIRSTATE_FILENAME)
271 271 newspec = repo.svfs.tryread(FILENAME)
272 272
273 273 oldincludes, oldexcludes = parseconfig(repo.ui, oldspec)
274 274 newincludes, newexcludes = parseconfig(repo.ui, newspec)
275 275 oldmatch = match(repo.root, include=oldincludes, exclude=oldexcludes)
276 276 newmatch = match(repo.root, include=newincludes, exclude=newexcludes)
277 277 addedmatch = matchmod.differencematcher(newmatch, oldmatch)
278 278 removedmatch = matchmod.differencematcher(oldmatch, newmatch)
279 279
280 280 ds = repo.dirstate
281 281 lookup, status = ds.status(removedmatch, subrepos=[], ignored=False,
282 282 clean=True, unknown=False)
283 283 _deletecleanfiles(repo, status.clean)
284 284 trackeddirty = lookup + status.modified + status.added
285 285 for f in sorted(trackeddirty):
286 286 repo.ui.status(_('not deleting possibly dirty file %s\n') % f)
287 287 for f in status.clean + trackeddirty:
288 288 ds.drop(f)
289 289
290 290 repo.narrowpats = newincludes, newexcludes
291 291 repo._narrowmatch = newmatch
292 292 pctx = repo['.']
293 293 newfiles = [f for f in pctx.manifest().walk(addedmatch) if f not in ds]
294 294 for f in newfiles:
295 295 ds.normallookup(f)
296 296 _writeaddedfiles(repo, pctx, newfiles)
297
298 ds.write(tr)
General Comments 0
You need to be logged in to leave comments. Login now