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