comparison mercurial/repair.py @ 30775:513d68a90398

repair: implement requirements checking for upgrades This commit introduces functionality for upgrading a repository in place. The first part that's implemented is testing for upgrade "compatibility." This is done by examining repository requirements. There are 5 functions returning sets of requirements that control upgrading. Why so many functions? Mainly to support extensions. Functions are easier to monkeypatch than module variables. Astute readers will see that we don't support "manifestv2" and "treemanifest" requirements in the upgrade mechanism. I don't have a great answer for why other than this is a complex set of patches and I don't want to deal with the complexity of these experimental features just yet. We can teach the upgrade mechanism about them later, once the basic upgrade mechanism is in place. This commit also introduces the "upgraderepo" function. This will be our main routine for performing an in-place upgrade. Currently, it just implements requirements checking. The structure of some code in this function may look a bit weird (e.g. the inline function that is only called once). But this will make sense after future commits.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sun, 18 Dec 2016 16:16:54 -0800
parents 987dbe87aad6
children 3997edc4a86d
comparison
equal deleted inserted replaced
30774:eaa5607132a2 30775:513d68a90398
356 newobsstorefile = obsstore.svfs('obsstore', 'w', atomictemp=True) 356 newobsstorefile = obsstore.svfs('obsstore', 'w', atomictemp=True)
357 for bytes in obsolete.encodemarkers(left, True, obsstore._version): 357 for bytes in obsolete.encodemarkers(left, True, obsstore._version):
358 newobsstorefile.write(bytes) 358 newobsstorefile.write(bytes)
359 newobsstorefile.close() 359 newobsstorefile.close()
360 return n 360 return n
361
362 def upgraderequiredsourcerequirements(repo):
363 """Obtain requirements required to be present to upgrade a repo.
364
365 An upgrade will not be allowed if the repository doesn't have the
366 requirements returned by this function.
367 """
368 return set([
369 # Introduced in Mercurial 0.9.2.
370 'revlogv1',
371 # Introduced in Mercurial 0.9.2.
372 'store',
373 ])
374
375 def upgradeblocksourcerequirements(repo):
376 """Obtain requirements that will prevent an upgrade from occurring.
377
378 An upgrade cannot be performed if the source repository contains a
379 requirements in the returned set.
380 """
381 return set([
382 # The upgrade code does not yet support these experimental features.
383 # This is an artificial limitation.
384 'manifestv2',
385 'treemanifest',
386 # This was a precursor to generaldelta and was never enabled by default.
387 # It should (hopefully) not exist in the wild.
388 'parentdelta',
389 # Upgrade should operate on the actual store, not the shared link.
390 'shared',
391 ])
392
393 def upgradesupportremovedrequirements(repo):
394 """Obtain requirements that can be removed during an upgrade.
395
396 If an upgrade were to create a repository that dropped a requirement,
397 the dropped requirement must appear in the returned set for the upgrade
398 to be allowed.
399 """
400 return set()
401
402 def upgradesupporteddestrequirements(repo):
403 """Obtain requirements that upgrade supports in the destination.
404
405 If the result of the upgrade would create requirements not in this set,
406 the upgrade is disallowed.
407
408 Extensions should monkeypatch this to add their custom requirements.
409 """
410 return set([
411 'dotencode',
412 'fncache',
413 'generaldelta',
414 'revlogv1',
415 'store',
416 ])
417
418 def upgradeallowednewrequirements(repo):
419 """Obtain requirements that can be added to a repository during upgrade.
420
421 This is used to disallow proposed requirements from being added when
422 they weren't present before.
423
424 We use a list of allowed requirement additions instead of a list of known
425 bad additions because the whitelist approach is safer and will prevent
426 future, unknown requirements from accidentally being added.
427 """
428 return set([
429 'dotencode',
430 'fncache',
431 'generaldelta',
432 ])
433
434 def upgraderepo(ui, repo, run=False, optimize=None):
435 """Upgrade a repository in place."""
436 # Avoid cycle: cmdutil -> repair -> localrepo -> cmdutil
437 from . import localrepo
438
439 repo = repo.unfiltered()
440
441 # Ensure the repository can be upgraded.
442 missingreqs = upgraderequiredsourcerequirements(repo) - repo.requirements
443 if missingreqs:
444 raise error.Abort(_('cannot upgrade repository; requirement '
445 'missing: %s') % _(', ').join(sorted(missingreqs)))
446
447 blockedreqs = upgradeblocksourcerequirements(repo) & repo.requirements
448 if blockedreqs:
449 raise error.Abort(_('cannot upgrade repository; unsupported source '
450 'requirement: %s') %
451 _(', ').join(sorted(blockedreqs)))
452
453 # FUTURE there is potentially a need to control the wanted requirements via
454 # command arguments or via an extension hook point.
455 newreqs = localrepo.newreporequirements(repo)
456
457 noremovereqs = (repo.requirements - newreqs -
458 upgradesupportremovedrequirements(repo))
459 if noremovereqs:
460 raise error.Abort(_('cannot upgrade repository; requirement would be '
461 'removed: %s') % _(', ').join(sorted(noremovereqs)))
462
463 noaddreqs = (newreqs - repo.requirements -
464 upgradeallowednewrequirements(repo))
465 if noaddreqs:
466 raise error.Abort(_('cannot upgrade repository; do not support adding '
467 'requirement: %s') %
468 _(', ').join(sorted(noaddreqs)))
469
470 unsupportedreqs = newreqs - upgradesupporteddestrequirements(repo)
471 if unsupportedreqs:
472 raise error.Abort(_('cannot upgrade repository; do not support '
473 'destination requirement: %s') %
474 _(', ').join(sorted(unsupportedreqs)))
475
476 def printrequirements():
477 ui.write(_('requirements\n'))
478 ui.write(_(' preserved: %s\n') %
479 _(', ').join(sorted(newreqs & repo.requirements)))
480
481 if repo.requirements - newreqs:
482 ui.write(_(' removed: %s\n') %
483 _(', ').join(sorted(repo.requirements - newreqs)))
484
485 if newreqs - repo.requirements:
486 ui.write(_(' added: %s\n') %
487 _(', ').join(sorted(newreqs - repo.requirements)))
488
489 ui.write('\n')
490
491 if not run:
492 ui.write(_('performing an upgrade with "--run" will make the following '
493 'changes:\n\n'))
494
495 printrequirements()