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