##// END OF EJS Templates
narrow: build the known set of nodes only when ellipses is enabled...
Pulkit Goyal -
r39560:294c5714 default
parent child Browse files
Show More
@@ -1,449 +1,452 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 cmdutil,
15 15 commands,
16 16 discovery,
17 17 encoding,
18 18 error,
19 19 exchange,
20 20 extensions,
21 21 hg,
22 22 merge,
23 23 narrowspec,
24 24 node,
25 25 pycompat,
26 26 registrar,
27 27 repair,
28 28 repository,
29 29 repoview,
30 30 sparse,
31 31 util,
32 32 )
33 33
34 34 from . import (
35 35 narrowbundle2,
36 36 )
37 37
38 38 table = {}
39 39 command = registrar.command(table)
40 40
41 41 def setup():
42 42 """Wraps user-facing mercurial commands with narrow-aware versions."""
43 43
44 44 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
45 45 entry[1].append(('', 'narrow', None,
46 46 _("create a narrow clone of select files")))
47 47 entry[1].append(('', 'depth', '',
48 48 _("limit the history fetched by distance from heads")))
49 49 entry[1].append(('', 'narrowspec', '',
50 50 _("read narrowspecs from file")))
51 51 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
52 52 if 'sparse' not in extensions.enabled():
53 53 entry[1].append(('', 'include', [],
54 54 _("specifically fetch this file/directory")))
55 55 entry[1].append(
56 56 ('', 'exclude', [],
57 57 _("do not fetch this file/directory, even if included")))
58 58
59 59 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
60 60 entry[1].append(('', 'depth', '',
61 61 _("limit the history fetched by distance from heads")))
62 62
63 63 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
64 64
65 65 def expandpull(pullop, includepats, excludepats):
66 66 if not narrowspec.needsexpansion(includepats):
67 67 return includepats, excludepats
68 68
69 69 heads = pullop.heads or pullop.rheads
70 70 includepats, excludepats = pullop.remote.expandnarrow(
71 71 includepats, excludepats, heads)
72 72 pullop.repo.ui.debug('Expanded narrowspec to inc=%s, exc=%s\n' % (
73 73 includepats, excludepats))
74 74 return set(includepats), set(excludepats)
75 75
76 76 def clonenarrowcmd(orig, ui, repo, *args, **opts):
77 77 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
78 78 opts = pycompat.byteskwargs(opts)
79 79 wrappedextraprepare = util.nullcontextmanager()
80 80 opts_narrow = opts['narrow']
81 81 narrowspecfile = opts['narrowspec']
82 82
83 83 if narrowspecfile:
84 84 filepath = os.path.join(pycompat.getcwd(), narrowspecfile)
85 85 ui.status(_("reading narrowspec from '%s'\n") % filepath)
86 86 try:
87 87 fdata = util.readfile(filepath)
88 88 except IOError as inst:
89 89 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
90 90 (filepath, encoding.strtolocal(inst.strerror)))
91 91
92 92 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
93 93 if profiles:
94 94 raise error.Abort(_("cannot specify other files using '%include' in"
95 95 " narrowspec"))
96 96
97 97 # narrowspec is passed so we should assume that user wants narrow clone
98 98 opts_narrow = True
99 99 opts['include'].extend(includes)
100 100 opts['exclude'].extend(excludes)
101 101
102 102 if opts_narrow:
103 103 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
104 104 # Create narrow spec patterns from clone flags
105 105 includepats = narrowspec.parsepatterns(opts['include'])
106 106 excludepats = narrowspec.parsepatterns(opts['exclude'])
107 107
108 108 # If necessary, ask the server to expand the narrowspec.
109 109 includepats, excludepats = expandpull(
110 110 pullop, includepats, excludepats)
111 111
112 112 if not includepats and excludepats:
113 113 # If nothing was included, we assume the user meant to include
114 114 # everything, except what they asked to exclude.
115 115 includepats = {'path:.'}
116 116
117 117 pullop.repo.setnarrowpats(includepats, excludepats)
118 118
119 119 # This will populate 'includepats' etc with the values from the
120 120 # narrowspec we just saved.
121 121 orig(pullop, kwargs)
122 122
123 123 if opts.get('depth'):
124 124 kwargs['depth'] = opts['depth']
125 125 wrappedextraprepare = extensions.wrappedfunction(exchange,
126 126 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
127 127
128 128 def pullnarrow(orig, repo, *args, **kwargs):
129 129 if opts_narrow:
130 130 repo.requirements.add(repository.NARROW_REQUIREMENT)
131 131 repo._writerequirements()
132 132
133 133 return orig(repo, *args, **kwargs)
134 134
135 135 wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
136 136
137 137 with wrappedextraprepare, wrappedpull:
138 138 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
139 139
140 140 def pullnarrowcmd(orig, ui, repo, *args, **opts):
141 141 """Wraps pull command to allow modifying narrow spec."""
142 142 wrappedextraprepare = util.nullcontextmanager()
143 143 if repository.NARROW_REQUIREMENT in repo.requirements:
144 144
145 145 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
146 146 orig(pullop, kwargs)
147 147 if opts.get(r'depth'):
148 148 kwargs['depth'] = opts[r'depth']
149 149 wrappedextraprepare = extensions.wrappedfunction(exchange,
150 150 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
151 151
152 152 with wrappedextraprepare:
153 153 return orig(ui, repo, *args, **opts)
154 154
155 155 def archivenarrowcmd(orig, ui, repo, *args, **opts):
156 156 """Wraps archive command to narrow the default includes."""
157 157 if repository.NARROW_REQUIREMENT in repo.requirements:
158 158 repo_includes, repo_excludes = repo.narrowpats
159 159 includes = set(opts.get(r'include', []))
160 160 excludes = set(opts.get(r'exclude', []))
161 161 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
162 162 includes, excludes, repo_includes, repo_excludes)
163 163 if includes:
164 164 opts[r'include'] = includes
165 165 if excludes:
166 166 opts[r'exclude'] = excludes
167 167 return orig(ui, repo, *args, **opts)
168 168
169 169 def pullbundle2extraprepare(orig, pullop, kwargs):
170 170 repo = pullop.repo
171 171 if repository.NARROW_REQUIREMENT not in repo.requirements:
172 172 return orig(pullop, kwargs)
173 173
174 174 if narrowbundle2.NARROWCAP not in pullop.remotebundle2caps:
175 175 raise error.Abort(_("server doesn't support narrow clones"))
176 176 orig(pullop, kwargs)
177 177 kwargs['narrow'] = True
178 178 include, exclude = repo.narrowpats
179 179 kwargs['oldincludepats'] = include
180 180 kwargs['oldexcludepats'] = exclude
181 181 kwargs['includepats'] = include
182 182 kwargs['excludepats'] = exclude
183 kwargs['known'] = [node.hex(ctx.node()) for ctx in
184 repo.set('::%ln', pullop.common)
185 if ctx.node() != node.nullid]
186 if not kwargs['known']:
187 # Mercurial serialized an empty list as '' and deserializes it as
188 # [''], so delete it instead to avoid handling the empty string on the
189 # server.
190 del kwargs['known']
183 # calculate known nodes only in ellipses cases because in non-ellipses cases
184 # we have all the nodes
185 if narrowbundle2.ELLIPSESCAP in pullop.remote.capabilities():
186 kwargs['known'] = [node.hex(ctx.node()) for ctx in
187 repo.set('::%ln', pullop.common)
188 if ctx.node() != node.nullid]
189 if not kwargs['known']:
190 # Mercurial serializes an empty list as '' and deserializes it as
191 # [''], so delete it instead to avoid handling the empty string on
192 # the server.
193 del kwargs['known']
191 194
192 195 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
193 196 pullbundle2extraprepare)
194 197
195 198 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
196 199 newincludes, newexcludes, force):
197 200 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
198 201 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
199 202
200 203 # This is essentially doing "hg outgoing" to find all local-only
201 204 # commits. We will then check that the local-only commits don't
202 205 # have any changes to files that will be untracked.
203 206 unfi = repo.unfiltered()
204 207 outgoing = discovery.findcommonoutgoing(unfi, remote,
205 208 commoninc=commoninc)
206 209 ui.status(_('looking for local changes to affected paths\n'))
207 210 localnodes = []
208 211 for n in itertools.chain(outgoing.missing, outgoing.excluded):
209 212 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
210 213 localnodes.append(n)
211 214 revstostrip = unfi.revs('descendants(%ln)', localnodes)
212 215 hiddenrevs = repoview.filterrevs(repo, 'visible')
213 216 visibletostrip = list(repo.changelog.node(r)
214 217 for r in (revstostrip - hiddenrevs))
215 218 if visibletostrip:
216 219 ui.status(_('The following changeset(s) or their ancestors have '
217 220 'local changes not on the remote:\n'))
218 221 maxnodes = 10
219 222 if ui.verbose or len(visibletostrip) <= maxnodes:
220 223 for n in visibletostrip:
221 224 ui.status('%s\n' % node.short(n))
222 225 else:
223 226 for n in visibletostrip[:maxnodes]:
224 227 ui.status('%s\n' % node.short(n))
225 228 ui.status(_('...and %d more, use --verbose to list all\n') %
226 229 (len(visibletostrip) - maxnodes))
227 230 if not force:
228 231 raise error.Abort(_('local changes found'),
229 232 hint=_('use --force-delete-local-changes to '
230 233 'ignore'))
231 234
232 235 with ui.uninterruptable():
233 236 if revstostrip:
234 237 tostrip = [unfi.changelog.node(r) for r in revstostrip]
235 238 if repo['.'].node() in tostrip:
236 239 # stripping working copy, so move to a different commit first
237 240 urev = max(repo.revs('(::%n) - %ln + null',
238 241 repo['.'].node(), visibletostrip))
239 242 hg.clean(repo, urev)
240 243 repair.strip(ui, unfi, tostrip, topic='narrow')
241 244
242 245 todelete = []
243 246 for f, f2, size in repo.store.datafiles():
244 247 if f.startswith('data/'):
245 248 file = f[5:-2]
246 249 if not newmatch(file):
247 250 todelete.append(f)
248 251 elif f.startswith('meta/'):
249 252 dir = f[5:-13]
250 253 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
251 254 include = True
252 255 for d in dirs:
253 256 visit = newmatch.visitdir(d)
254 257 if not visit:
255 258 include = False
256 259 break
257 260 if visit == 'all':
258 261 break
259 262 if not include:
260 263 todelete.append(f)
261 264
262 265 repo.destroying()
263 266
264 267 with repo.transaction("narrowing"):
265 268 for f in todelete:
266 269 ui.status(_('deleting %s\n') % f)
267 270 util.unlinkpath(repo.svfs.join(f))
268 271 repo.store.markremoved(f)
269 272
270 273 for f in repo.dirstate:
271 274 if not newmatch(f):
272 275 repo.dirstate.drop(f)
273 276 repo.wvfs.unlinkpath(f)
274 277 repo.setnarrowpats(newincludes, newexcludes)
275 278
276 279 repo.destroyed()
277 280
278 281 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
279 282 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
280 283
281 284 # TODO(martinvonz): Get expansion working with widening/narrowing.
282 285 if narrowspec.needsexpansion(newincludes):
283 286 raise error.Abort('Expansion not yet supported on pull')
284 287
285 288 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
286 289 orig(pullop, kwargs)
287 290 # The old{in,ex}cludepats have already been set by orig()
288 291 kwargs['includepats'] = newincludes
289 292 kwargs['excludepats'] = newexcludes
290 293 kwargs['widen'] = True
291 294 wrappedextraprepare = extensions.wrappedfunction(exchange,
292 295 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
293 296
294 297 # define a function that narrowbundle2 can call after creating the
295 298 # backup bundle, but before applying the bundle from the server
296 299 def setnewnarrowpats():
297 300 repo.setnarrowpats(newincludes, newexcludes)
298 301 repo.setnewnarrowpats = setnewnarrowpats
299 302
300 303 with ui.uninterruptable():
301 304 ds = repo.dirstate
302 305 p1, p2 = ds.p1(), ds.p2()
303 306 with ds.parentchange():
304 307 ds.setparents(node.nullid, node.nullid)
305 308 common = commoninc[0]
306 309 with wrappedextraprepare:
307 310 exchange.pull(repo, remote, heads=common)
308 311 with ds.parentchange():
309 312 ds.setparents(p1, p2)
310 313
311 314 repo.setnewnarrowpats()
312 315 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
313 316 addgaction = actions['g'].append
314 317
315 318 mf = repo['.'].manifest().matches(newmatch)
316 319 for f, fn in mf.iteritems():
317 320 if f not in repo.dirstate:
318 321 addgaction((f, (mf.flags(f), False),
319 322 "add from widened narrow clone"))
320 323
321 324 merge.applyupdates(repo, actions, wctx=repo[None],
322 325 mctx=repo['.'], overwrite=False)
323 326 merge.recordupdates(repo, actions, branchmerge=False)
324 327
325 328 # TODO(rdamazio): Make new matcher format and update description
326 329 @command('tracked',
327 330 [('', 'addinclude', [], _('new paths to include')),
328 331 ('', 'removeinclude', [], _('old paths to no longer include')),
329 332 ('', 'addexclude', [], _('new paths to exclude')),
330 333 ('', 'import-rules', '', _('import narrowspecs from a file')),
331 334 ('', 'removeexclude', [], _('old paths to no longer exclude')),
332 335 ('', 'clear', False, _('whether to replace the existing narrowspec')),
333 336 ('', 'force-delete-local-changes', False,
334 337 _('forces deletion of local changes when narrowing')),
335 338 ] + commands.remoteopts,
336 339 _('[OPTIONS]... [REMOTE]'),
337 340 inferrepo=True)
338 341 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
339 342 """show or change the current narrowspec
340 343
341 344 With no argument, shows the current narrowspec entries, one per line. Each
342 345 line will be prefixed with 'I' or 'X' for included or excluded patterns,
343 346 respectively.
344 347
345 348 The narrowspec is comprised of expressions to match remote files and/or
346 349 directories that should be pulled into your client.
347 350 The narrowspec has *include* and *exclude* expressions, with excludes always
348 351 trumping includes: that is, if a file matches an exclude expression, it will
349 352 be excluded even if it also matches an include expression.
350 353 Excluding files that were never included has no effect.
351 354
352 355 Each included or excluded entry is in the format described by
353 356 'hg help patterns'.
354 357
355 358 The options allow you to add or remove included and excluded expressions.
356 359
357 360 If --clear is specified, then all previous includes and excludes are DROPPED
358 361 and replaced by the new ones specified to --addinclude and --addexclude.
359 362 If --clear is specified without any further options, the narrowspec will be
360 363 empty and will not match any files.
361 364 """
362 365 opts = pycompat.byteskwargs(opts)
363 366 if repository.NARROW_REQUIREMENT not in repo.requirements:
364 367 ui.warn(_('The narrow command is only supported on respositories cloned'
365 368 ' with --narrow.\n'))
366 369 return 1
367 370
368 371 # Before supporting, decide whether it "hg tracked --clear" should mean
369 372 # tracking no paths or all paths.
370 373 if opts['clear']:
371 374 ui.warn(_('The --clear option is not yet supported.\n'))
372 375 return 1
373 376
374 377 # import rules from a file
375 378 newrules = opts.get('import_rules')
376 379 if newrules:
377 380 try:
378 381 filepath = os.path.join(pycompat.getcwd(), newrules)
379 382 fdata = util.readfile(filepath)
380 383 except IOError as inst:
381 384 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
382 385 (filepath, encoding.strtolocal(inst.strerror)))
383 386 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
384 387 'narrow')
385 388 if profiles:
386 389 raise error.Abort(_("including other spec files using '%include' "
387 390 "is not supported in narrowspec"))
388 391 opts['addinclude'].extend(includepats)
389 392 opts['addexclude'].extend(excludepats)
390 393
391 394 if narrowspec.needsexpansion(opts['addinclude'] + opts['addexclude']):
392 395 raise error.Abort('Expansion not yet supported on widen/narrow')
393 396
394 397 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
395 398 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
396 399 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
397 400 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
398 401 widening = addedincludes or removedexcludes
399 402 narrowing = removedincludes or addedexcludes
400 403 only_show = not widening and not narrowing
401 404
402 405 # Only print the current narrowspec.
403 406 if only_show:
404 407 include, exclude = repo.narrowpats
405 408
406 409 ui.pager('tracked')
407 410 fm = ui.formatter('narrow', opts)
408 411 for i in sorted(include):
409 412 fm.startitem()
410 413 fm.write('status', '%s ', 'I', label='narrow.included')
411 414 fm.write('pat', '%s\n', i, label='narrow.included')
412 415 for i in sorted(exclude):
413 416 fm.startitem()
414 417 fm.write('status', '%s ', 'X', label='narrow.excluded')
415 418 fm.write('pat', '%s\n', i, label='narrow.excluded')
416 419 fm.end()
417 420 return 0
418 421
419 422 with repo.wlock(), repo.lock():
420 423 cmdutil.bailifchanged(repo)
421 424
422 425 # Find the revisions we have in common with the remote. These will
423 426 # be used for finding local-only changes for narrowing. They will
424 427 # also define the set of revisions to update for widening.
425 428 remotepath = ui.expandpath(remotepath or 'default')
426 429 url, branches = hg.parseurl(remotepath)
427 430 ui.status(_('comparing with %s\n') % util.hidepassword(url))
428 431 remote = hg.peer(repo, opts, url)
429 432 commoninc = discovery.findcommonincoming(repo, remote)
430 433
431 434 oldincludes, oldexcludes = repo.narrowpats
432 435 if narrowing:
433 436 newincludes = oldincludes - removedincludes
434 437 newexcludes = oldexcludes | addedexcludes
435 438 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
436 439 newincludes, newexcludes,
437 440 opts['force_delete_local_changes'])
438 441 # _narrow() updated the narrowspec and _widen() below needs to
439 442 # use the updated values as its base (otherwise removed includes
440 443 # and addedexcludes will be lost in the resulting narrowspec)
441 444 oldincludes = newincludes
442 445 oldexcludes = newexcludes
443 446
444 447 if widening:
445 448 newincludes = oldincludes | addedincludes
446 449 newexcludes = oldexcludes - removedexcludes
447 450 _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
448 451
449 452 return 0
General Comments 0
You need to be logged in to leave comments. Login now