Mercurial > hg
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() |