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