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