##// END OF EJS Templates
narrow: replace filtering in list comprehension by set operations...
Martin von Zweigbergk -
r40465:5d8f2914 default
parent child Browse files
Show More
@@ -1,469 +1,469 b''
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 kwargs['includepats'] = include
145 145 kwargs['excludepats'] = exclude
146 146 # calculate known nodes only in ellipses cases because in non-ellipses cases
147 147 # we have all the nodes
148 148 if wireprototypes.ELLIPSESCAP in pullop.remote.capabilities():
149 149 kwargs['known'] = [node.hex(ctx.node()) for ctx in
150 150 repo.set('::%ln', pullop.common)
151 151 if ctx.node() != node.nullid]
152 152 if not kwargs['known']:
153 153 # Mercurial serializes an empty list as '' and deserializes it as
154 154 # [''], so delete it instead to avoid handling the empty string on
155 155 # the server.
156 156 del kwargs['known']
157 157
158 158 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
159 159 pullbundle2extraprepare)
160 160
161 161 # This is an extension point for filesystems that need to do something other
162 162 # than just blindly unlink the files. It's not clear what arguments would be
163 163 # useful, so we're passing in a fair number of them, some of them redundant.
164 164 def _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes, newexcludes,
165 165 oldmatch, newmatch):
166 166 for f in repo.dirstate:
167 167 if not newmatch(f):
168 168 repo.dirstate.drop(f)
169 169 repo.wvfs.unlinkpath(f)
170 170
171 171 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
172 172 newincludes, newexcludes, force):
173 173 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
174 174 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
175 175
176 176 # This is essentially doing "hg outgoing" to find all local-only
177 177 # commits. We will then check that the local-only commits don't
178 178 # have any changes to files that will be untracked.
179 179 unfi = repo.unfiltered()
180 180 outgoing = discovery.findcommonoutgoing(unfi, remote,
181 181 commoninc=commoninc)
182 182 ui.status(_('looking for local changes to affected paths\n'))
183 183 localnodes = []
184 184 for n in itertools.chain(outgoing.missing, outgoing.excluded):
185 185 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
186 186 localnodes.append(n)
187 187 revstostrip = unfi.revs('descendants(%ln)', localnodes)
188 188 hiddenrevs = repoview.filterrevs(repo, 'visible')
189 189 visibletostrip = list(repo.changelog.node(r)
190 190 for r in (revstostrip - hiddenrevs))
191 191 if visibletostrip:
192 192 ui.status(_('The following changeset(s) or their ancestors have '
193 193 'local changes not on the remote:\n'))
194 194 maxnodes = 10
195 195 if ui.verbose or len(visibletostrip) <= maxnodes:
196 196 for n in visibletostrip:
197 197 ui.status('%s\n' % node.short(n))
198 198 else:
199 199 for n in visibletostrip[:maxnodes]:
200 200 ui.status('%s\n' % node.short(n))
201 201 ui.status(_('...and %d more, use --verbose to list all\n') %
202 202 (len(visibletostrip) - maxnodes))
203 203 if not force:
204 204 raise error.Abort(_('local changes found'),
205 205 hint=_('use --force-delete-local-changes to '
206 206 'ignore'))
207 207
208 208 with ui.uninterruptable():
209 209 if revstostrip:
210 210 tostrip = [unfi.changelog.node(r) for r in revstostrip]
211 211 if repo['.'].node() in tostrip:
212 212 # stripping working copy, so move to a different commit first
213 213 urev = max(repo.revs('(::%n) - %ln + null',
214 214 repo['.'].node(), visibletostrip))
215 215 hg.clean(repo, urev)
216 216 repair.strip(ui, unfi, tostrip, topic='narrow')
217 217
218 218 todelete = []
219 219 for f, f2, size in repo.store.datafiles():
220 220 if f.startswith('data/'):
221 221 file = f[5:-2]
222 222 if not newmatch(file):
223 223 todelete.append(f)
224 224 elif f.startswith('meta/'):
225 225 dir = f[5:-13]
226 226 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
227 227 include = True
228 228 for d in dirs:
229 229 visit = newmatch.visitdir(d)
230 230 if not visit:
231 231 include = False
232 232 break
233 233 if visit == 'all':
234 234 break
235 235 if not include:
236 236 todelete.append(f)
237 237
238 238 repo.destroying()
239 239
240 240 with repo.transaction("narrowing"):
241 241 for f in todelete:
242 242 ui.status(_('deleting %s\n') % f)
243 243 util.unlinkpath(repo.svfs.join(f))
244 244 repo.store.markremoved(f)
245 245
246 246 _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes,
247 247 newexcludes, oldmatch, newmatch)
248 248 repo.setnarrowpats(newincludes, newexcludes)
249 249
250 250 repo.destroyed()
251 251
252 252 def _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
253 253 newincludes, newexcludes):
254 254 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
255 255
256 256 # for now we assume that if a server has ellipses enabled, we will be
257 257 # exchanging ellipses nodes. In future we should add ellipses as a client
258 258 # side requirement (maybe) to distinguish a client is shallow or not and
259 259 # then send that information to server whether we want ellipses or not.
260 260 # Theoretically a non-ellipses repo should be able to use narrow
261 261 # functionality from an ellipses enabled server
262 262 ellipsesremote = wireprototypes.ELLIPSESCAP in remote.capabilities()
263 263
264 264 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
265 265 orig(pullop, kwargs)
266 266 # The old{in,ex}cludepats have already been set by orig()
267 267 kwargs['includepats'] = newincludes
268 268 kwargs['excludepats'] = newexcludes
269 269 wrappedextraprepare = extensions.wrappedfunction(exchange,
270 270 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
271 271
272 272 # define a function that narrowbundle2 can call after creating the
273 273 # backup bundle, but before applying the bundle from the server
274 274 def setnewnarrowpats():
275 275 repo.setnarrowpats(newincludes, newexcludes)
276 276 repo.setnewnarrowpats = setnewnarrowpats
277 277 # silence the devel-warning of applying an empty changegroup
278 278 overrides = {('devel', 'all-warnings'): False}
279 279
280 280 with ui.uninterruptable():
281 281 common = commoninc[0]
282 282 if ellipsesremote:
283 283 ds = repo.dirstate
284 284 p1, p2 = ds.p1(), ds.p2()
285 285 with ds.parentchange():
286 286 ds.setparents(node.nullid, node.nullid)
287 287 with wrappedextraprepare,\
288 288 repo.ui.configoverride(overrides, 'widen'):
289 289 exchange.pull(repo, remote, heads=common)
290 290 with ds.parentchange():
291 291 ds.setparents(p1, p2)
292 292 else:
293 293 with remote.commandexecutor() as e:
294 294 bundle = e.callcommand('narrow_widen', {
295 295 'oldincludes': oldincludes,
296 296 'oldexcludes': oldexcludes,
297 297 'newincludes': newincludes,
298 298 'newexcludes': newexcludes,
299 299 'cgversion': '03',
300 300 'commonheads': common,
301 301 'known': [],
302 302 'ellipses': False,
303 303 }).result()
304 304
305 305 with repo.transaction('widening') as tr,\
306 306 repo.ui.configoverride(overrides, 'widen'):
307 307 tgetter = lambda: tr
308 308 bundle2.processbundle(repo, bundle,
309 309 transactiongetter=tgetter)
310 310
311 311 repo.setnewnarrowpats()
312 312 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
313 313 addgaction = actions['g'].append
314 314
315 315 mf = repo['.'].manifest().matches(newmatch)
316 316 for f, fn in mf.iteritems():
317 317 if f not in repo.dirstate:
318 318 addgaction((f, (mf.flags(f), False),
319 319 "add from widened narrow clone"))
320 320
321 321 merge.applyupdates(repo, actions, wctx=repo[None],
322 322 mctx=repo['.'], overwrite=False)
323 323 merge.recordupdates(repo, actions, branchmerge=False)
324 324
325 325 # TODO(rdamazio): Make new matcher format and update description
326 326 @command('tracked',
327 327 [('', 'addinclude', [], _('new paths to include')),
328 328 ('', 'removeinclude', [], _('old paths to no longer include')),
329 329 ('', 'addexclude', [], _('new paths to exclude')),
330 330 ('', 'import-rules', '', _('import narrowspecs from a file')),
331 331 ('', 'removeexclude', [], _('old paths to no longer exclude')),
332 332 ('', 'clear', False, _('whether to replace the existing narrowspec')),
333 333 ('', 'force-delete-local-changes', False,
334 334 _('forces deletion of local changes when narrowing')),
335 335 ] + commands.remoteopts,
336 336 _('[OPTIONS]... [REMOTE]'),
337 337 inferrepo=True)
338 338 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
339 339 """show or change the current narrowspec
340 340
341 341 With no argument, shows the current narrowspec entries, one per line. Each
342 342 line will be prefixed with 'I' or 'X' for included or excluded patterns,
343 343 respectively.
344 344
345 345 The narrowspec is comprised of expressions to match remote files and/or
346 346 directories that should be pulled into your client.
347 347 The narrowspec has *include* and *exclude* expressions, with excludes always
348 348 trumping includes: that is, if a file matches an exclude expression, it will
349 349 be excluded even if it also matches an include expression.
350 350 Excluding files that were never included has no effect.
351 351
352 352 Each included or excluded entry is in the format described by
353 353 'hg help patterns'.
354 354
355 355 The options allow you to add or remove included and excluded expressions.
356 356
357 357 If --clear is specified, then all previous includes and excludes are DROPPED
358 358 and replaced by the new ones specified to --addinclude and --addexclude.
359 359 If --clear is specified without any further options, the narrowspec will be
360 360 empty and will not match any files.
361 361 """
362 362 opts = pycompat.byteskwargs(opts)
363 363 if repository.NARROW_REQUIREMENT not in repo.requirements:
364 364 ui.warn(_('The narrow command is only supported on respositories cloned'
365 365 ' with --narrow.\n'))
366 366 return 1
367 367
368 368 # Before supporting, decide whether it "hg tracked --clear" should mean
369 369 # tracking no paths or all paths.
370 370 if opts['clear']:
371 371 ui.warn(_('The --clear option is not yet supported.\n'))
372 372 return 1
373 373
374 374 # import rules from a file
375 375 newrules = opts.get('import_rules')
376 376 if newrules:
377 377 try:
378 378 filepath = os.path.join(encoding.getcwd(), newrules)
379 379 fdata = util.readfile(filepath)
380 380 except IOError as inst:
381 381 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
382 382 (filepath, encoding.strtolocal(inst.strerror)))
383 383 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
384 384 'narrow')
385 385 if profiles:
386 386 raise error.Abort(_("including other spec files using '%include' "
387 387 "is not supported in narrowspec"))
388 388 opts['addinclude'].extend(includepats)
389 389 opts['addexclude'].extend(excludepats)
390 390
391 391 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
392 392 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
393 393 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
394 394 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
395 395
396 396 only_show = not (addedincludes or removedincludes or addedexcludes or
397 397 removedexcludes or newrules)
398 398
399 399 oldincludes, oldexcludes = repo.narrowpats
400 400
401 401 # filter the user passed additions and deletions into actual additions and
402 402 # deletions of excludes and includes
403 addedincludes = set([i for i in addedincludes if i not in oldincludes])
404 removedincludes = set([i for i in removedincludes if i in oldincludes])
405 addedexcludes = set([i for i in addedexcludes if i not in oldexcludes])
406 removedexcludes = set([i for i in removedexcludes if i in oldexcludes])
403 addedincludes -= oldincludes
404 removedincludes &= oldincludes
405 addedexcludes -= oldexcludes
406 removedexcludes &= oldexcludes
407 407
408 408 widening = addedincludes or removedexcludes
409 409 narrowing = removedincludes or addedexcludes
410 410
411 411 # Only print the current narrowspec.
412 412 if only_show:
413 413 include, exclude = repo.narrowpats
414 414
415 415 ui.pager('tracked')
416 416 fm = ui.formatter('narrow', opts)
417 417 for i in sorted(include):
418 418 fm.startitem()
419 419 fm.write('status', '%s ', 'I', label='narrow.included')
420 420 fm.write('pat', '%s\n', i, label='narrow.included')
421 421 for i in sorted(exclude):
422 422 fm.startitem()
423 423 fm.write('status', '%s ', 'X', label='narrow.excluded')
424 424 fm.write('pat', '%s\n', i, label='narrow.excluded')
425 425 fm.end()
426 426 return 0
427 427
428 428 if not widening and not narrowing:
429 429 ui.status(_("nothing to widen or narrow\n"))
430 430 return 0
431 431
432 432 with repo.wlock(), repo.lock():
433 433 cmdutil.bailifchanged(repo)
434 434
435 435 # Find the revisions we have in common with the remote. These will
436 436 # be used for finding local-only changes for narrowing. They will
437 437 # also define the set of revisions to update for widening.
438 438 remotepath = ui.expandpath(remotepath or 'default')
439 439 url, branches = hg.parseurl(remotepath)
440 440 ui.status(_('comparing with %s\n') % util.hidepassword(url))
441 441 remote = hg.peer(repo, opts, url)
442 442
443 443 # check narrow support before doing anything if widening needs to be
444 444 # performed. In future we should also abort if client is ellipses and
445 445 # server does not support ellipses
446 446 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
447 447 raise error.Abort(_("server does not support narrow clones"))
448 448
449 449 commoninc = discovery.findcommonincoming(repo, remote)
450 450
451 451 if narrowing:
452 452 newincludes = oldincludes - removedincludes
453 453 newexcludes = oldexcludes | addedexcludes
454 454 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
455 455 newincludes, newexcludes,
456 456 opts['force_delete_local_changes'])
457 457 # _narrow() updated the narrowspec and _widen() below needs to
458 458 # use the updated values as its base (otherwise removed includes
459 459 # and addedexcludes will be lost in the resulting narrowspec)
460 460 oldincludes = newincludes
461 461 oldexcludes = newexcludes
462 462
463 463 if widening:
464 464 newincludes = oldincludes | addedincludes
465 465 newexcludes = oldexcludes - removedexcludes
466 466 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
467 467 newincludes, newexcludes)
468 468
469 469 return 0
General Comments 0
You need to be logged in to leave comments. Login now