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