Show More
@@ -0,0 +1,107 b'' | |||||
|
1 | # upgrade.py - functions for automatic upgrade of Mercurial repository | |||
|
2 | # | |||
|
3 | # Copyright (c) 2022-present, Pierre-Yves David | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | from ..i18n import _ | |||
|
8 | ||||
|
9 | from .. import ( | |||
|
10 | error, | |||
|
11 | requirements as requirementsmod, | |||
|
12 | scmutil, | |||
|
13 | ) | |||
|
14 | ||||
|
15 | ||||
|
16 | def get_share_safe_action(repo): | |||
|
17 | """return an automatic-upgrade action for `share-safe` if applicable | |||
|
18 | ||||
|
19 | If no action is needed, return None, otherwise return a callback to upgrade | |||
|
20 | or downgrade the repository according the configuration and repository | |||
|
21 | format. | |||
|
22 | """ | |||
|
23 | ui = repo.ui | |||
|
24 | requirements = repo.requirements | |||
|
25 | auto_upgrade_share_source = ui.configbool( | |||
|
26 | b'format', | |||
|
27 | b'use-share-safe.automatic-upgrade-of-mismatching-repositories', | |||
|
28 | ) | |||
|
29 | ||||
|
30 | action = None | |||
|
31 | ||||
|
32 | if ( | |||
|
33 | auto_upgrade_share_source | |||
|
34 | and requirementsmod.SHARED_REQUIREMENT not in requirements | |||
|
35 | ): | |||
|
36 | sf_config = ui.configbool(b'format', b'use-share-safe') | |||
|
37 | sf_local = requirementsmod.SHARESAFE_REQUIREMENT in requirements | |||
|
38 | if sf_config and not sf_local: | |||
|
39 | msg = _( | |||
|
40 | b"automatically upgrading repository to the `share-safe`" | |||
|
41 | b" feature\n" | |||
|
42 | ) | |||
|
43 | hint = b"(see `hg help config.format.use-share-safe` for details)\n" | |||
|
44 | ||||
|
45 | def action(): | |||
|
46 | if not ui.quiet: | |||
|
47 | ui.write_err(msg) | |||
|
48 | ui.write_err(hint) | |||
|
49 | requirements.add(requirementsmod.SHARESAFE_REQUIREMENT) | |||
|
50 | scmutil.writereporequirements(repo, requirements) | |||
|
51 | ||||
|
52 | elif sf_local and not sf_config: | |||
|
53 | msg = _( | |||
|
54 | b"automatically downgrading repository from the `share-safe`" | |||
|
55 | b" feature\n" | |||
|
56 | ) | |||
|
57 | hint = b"(see `hg help config.format.use-share-safe` for details)\n" | |||
|
58 | ||||
|
59 | def action(): | |||
|
60 | if not ui.quiet: | |||
|
61 | ui.write_err(msg) | |||
|
62 | ui.write_err(hint) | |||
|
63 | requirements.discard(requirementsmod.SHARESAFE_REQUIREMENT) | |||
|
64 | scmutil.writereporequirements(repo, requirements) | |||
|
65 | ||||
|
66 | return action | |||
|
67 | ||||
|
68 | ||||
|
69 | AUTO_UPGRADE_ACTIONS = [ | |||
|
70 | get_share_safe_action, | |||
|
71 | ] | |||
|
72 | ||||
|
73 | ||||
|
74 | def may_auto_upgrade(repo, maker_func): | |||
|
75 | """potentially perform auto-upgrade and return the final repository to use | |||
|
76 | ||||
|
77 | Auto-upgrade are "quick" repository upgrade that might automatically be run | |||
|
78 | by "any" repository access. See `hg help config.format` for automatic | |||
|
79 | upgrade documentation. | |||
|
80 | ||||
|
81 | note: each relevant upgrades are done one after the other for simplicity. | |||
|
82 | This avoid having repository is partially inconsistent state while | |||
|
83 | upgrading. | |||
|
84 | ||||
|
85 | repo: the current repository instance | |||
|
86 | maker_func: a factory function that can recreate a repository after an upgrade | |||
|
87 | """ | |||
|
88 | clear = False | |||
|
89 | ||||
|
90 | loop = 0 | |||
|
91 | ||||
|
92 | while not clear: | |||
|
93 | loop += 1 | |||
|
94 | if loop > 100: | |||
|
95 | # XXX basic protection against infinite loop, make it better. | |||
|
96 | raise error.ProgrammingError("Too many auto upgrade loops") | |||
|
97 | clear = True | |||
|
98 | for get_action in AUTO_UPGRADE_ACTIONS: | |||
|
99 | action = get_action(repo) | |||
|
100 | if action is not None: | |||
|
101 | clear = False | |||
|
102 | with repo.wlock(wait=False), repo.lock(wait=False): | |||
|
103 | action = get_action(repo) | |||
|
104 | if action is not None: | |||
|
105 | action() | |||
|
106 | repo = maker_func() | |||
|
107 | return repo |
@@ -1386,6 +1386,12 b' coreconfigitem(' | |||||
1386 | ) |
|
1386 | ) | |
1387 | coreconfigitem( |
|
1387 | coreconfigitem( | |
1388 | b'format', |
|
1388 | b'format', | |
|
1389 | b'use-share-safe.automatic-upgrade-of-mismatching-repositories', | |||
|
1390 | default=False, | |||
|
1391 | experimental=True, | |||
|
1392 | ) | |||
|
1393 | coreconfigitem( | |||
|
1394 | b'format', | |||
1389 | b'internal-phase', |
|
1395 | b'internal-phase', | |
1390 | default=False, |
|
1396 | default=False, | |
1391 | experimental=True, |
|
1397 | experimental=True, |
@@ -1032,6 +1032,24 b' https://www.mercurial-scm.org/wiki/Missi' | |||||
1032 |
|
1032 | |||
1033 | Enabled by default in Mercurial 6.1. |
|
1033 | Enabled by default in Mercurial 6.1. | |
1034 |
|
1034 | |||
|
1035 | ``use-share-safe.automatic-upgrade-of-mismatching-repositories`` | |||
|
1036 | When enabled, an automatic upgrade will be triggered when a repository format | |||
|
1037 | does not match its `use-share-safe` config. | |||
|
1038 | ||||
|
1039 | This is an advanced behavior that most users will not need. We recommend you | |||
|
1040 | don't use this unless you are a seasoned administrator of a Mercurial install | |||
|
1041 | base. | |||
|
1042 | ||||
|
1043 | Automatic upgrade means that any process accessing the repository will | |||
|
1044 | upgrade the repository format to use `share-safe`. This only triggers if a | |||
|
1045 | change is needed. This also applies to operation that would have been | |||
|
1046 | read-only (like hg status). | |||
|
1047 | ||||
|
1048 | This configuration will apply for moves in any direction, either adding the | |||
|
1049 | `share-safe` format if `format.use-share-safe=yes` or removing the | |||
|
1050 | `share-safe` requirement if `format.use-share-safe=no`. So we recommend | |||
|
1051 | setting both this value and `format.use-share-safe` at the same time. | |||
|
1052 | ||||
1035 | ``usestore`` |
|
1053 | ``usestore`` | |
1036 | Enable or disable the "store" repository format which improves |
|
1054 | Enable or disable the "store" repository format which improves | |
1037 | compatibility with systems that fold case or otherwise mangle |
|
1055 | compatibility with systems that fold case or otherwise mangle |
@@ -3516,11 +3516,20 b' def undoname(fn):' | |||||
3516 |
|
3516 | |||
3517 |
|
3517 | |||
3518 | def instance(ui, path, create, intents=None, createopts=None): |
|
3518 | def instance(ui, path, create, intents=None, createopts=None): | |
|
3519 | ||||
|
3520 | # prevent cyclic import localrepo -> upgrade -> localrepo | |||
|
3521 | from . import upgrade | |||
|
3522 | ||||
3519 | localpath = urlutil.urllocalpath(path) |
|
3523 | localpath = urlutil.urllocalpath(path) | |
3520 | if create: |
|
3524 | if create: | |
3521 | createrepository(ui, localpath, createopts=createopts) |
|
3525 | createrepository(ui, localpath, createopts=createopts) | |
3522 |
|
3526 | |||
3523 | return makelocalrepository(ui, localpath, intents=intents) |
|
3527 | def repo_maker(): | |
|
3528 | return makelocalrepository(ui, localpath, intents=intents) | |||
|
3529 | ||||
|
3530 | repo = repo_maker() | |||
|
3531 | repo = upgrade.may_auto_upgrade(repo, repo_maker) | |||
|
3532 | return repo | |||
3524 |
|
3533 | |||
3525 |
|
3534 | |||
3526 | def islocal(path): |
|
3535 | def islocal(path): |
@@ -19,6 +19,7 b' from . import (' | |||||
19 |
|
19 | |||
20 | from .upgrade_utils import ( |
|
20 | from .upgrade_utils import ( | |
21 | actions as upgrade_actions, |
|
21 | actions as upgrade_actions, | |
|
22 | auto_upgrade, | |||
22 | engine as upgrade_engine, |
|
23 | engine as upgrade_engine, | |
23 | ) |
|
24 | ) | |
24 |
|
25 | |||
@@ -26,6 +27,7 b' from .utils import (' | |||||
26 | stringutil, |
|
27 | stringutil, | |
27 | ) |
|
28 | ) | |
28 |
|
29 | |||
|
30 | may_auto_upgrade = auto_upgrade.may_auto_upgrade | |||
29 | allformatvariant = upgrade_actions.allformatvariant |
|
31 | allformatvariant = upgrade_actions.allformatvariant | |
30 |
|
32 | |||
31 |
|
33 |
@@ -7,10 +7,10 b' use clap::Arg;' | |||||
7 | use clap::ArgMatches; |
|
7 | use clap::ArgMatches; | |
8 | use format_bytes::{format_bytes, join}; |
|
8 | use format_bytes::{format_bytes, join}; | |
9 | use hg::config::{Config, ConfigSource}; |
|
9 | use hg::config::{Config, ConfigSource}; | |
10 | use hg::exit_codes; |
|
|||
11 | use hg::repo::{Repo, RepoError}; |
|
10 | use hg::repo::{Repo, RepoError}; | |
12 | use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes}; |
|
11 | use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes}; | |
13 | use hg::utils::SliceExt; |
|
12 | use hg::utils::SliceExt; | |
|
13 | use hg::{exit_codes, requirements}; | |||
14 | use std::collections::HashSet; |
|
14 | use std::collections::HashSet; | |
15 | use std::ffi::OsString; |
|
15 | use std::ffi::OsString; | |
16 | use std::os::unix::prelude::CommandExt; |
|
16 | use std::os::unix::prelude::CommandExt; | |
@@ -724,6 +724,50 b' fn check_extensions(config: &Config) -> ' | |||||
724 | } |
|
724 | } | |
725 | } |
|
725 | } | |
726 |
|
726 | |||
|
727 | /// Array of tuples of (auto upgrade conf, feature conf, local requirement) | |||
|
728 | const AUTO_UPGRADES: &[((&str, &str), (&str, &str), &str)] = &[ | |||
|
729 | ( | |||
|
730 | ("format", "use-share-safe.automatic-upgrade-of-mismatching-repositories"), | |||
|
731 | ("format", "use-share-safe"), | |||
|
732 | requirements::SHARESAFE_REQUIREMENT, | |||
|
733 | ), | |||
|
734 | ]; | |||
|
735 | ||||
|
736 | /// Mercurial allows users to automatically upgrade their repository. | |||
|
737 | /// `rhg` does not have the ability to upgrade yet, so fallback if an upgrade | |||
|
738 | /// is needed. | |||
|
739 | fn check_auto_upgrade( | |||
|
740 | config: &Config, | |||
|
741 | reqs: &HashSet<String>, | |||
|
742 | ) -> Result<(), CommandError> { | |||
|
743 | for (upgrade_conf, feature_conf, local_req) in AUTO_UPGRADES.iter() { | |||
|
744 | let auto_upgrade = config | |||
|
745 | .get_bool(upgrade_conf.0.as_bytes(), upgrade_conf.1.as_bytes())?; | |||
|
746 | ||||
|
747 | if auto_upgrade { | |||
|
748 | let want_it = config.get_bool( | |||
|
749 | feature_conf.0.as_bytes(), | |||
|
750 | feature_conf.1.as_bytes(), | |||
|
751 | )?; | |||
|
752 | let have_it = reqs.contains(*local_req); | |||
|
753 | ||||
|
754 | let action = match (want_it, have_it) { | |||
|
755 | (true, false) => Some("upgrade"), | |||
|
756 | (false, true) => Some("downgrade"), | |||
|
757 | _ => None, | |||
|
758 | }; | |||
|
759 | if let Some(action) = action { | |||
|
760 | let message = format!( | |||
|
761 | "automatic {} {}.{}", | |||
|
762 | action, upgrade_conf.0, upgrade_conf.1 | |||
|
763 | ); | |||
|
764 | return Err(CommandError::unsupported(message)); | |||
|
765 | } | |||
|
766 | } | |||
|
767 | } | |||
|
768 | Ok(()) | |||
|
769 | } | |||
|
770 | ||||
727 | fn check_unsupported( |
|
771 | fn check_unsupported( | |
728 | config: &Config, |
|
772 | config: &Config, | |
729 | repo: Result<&Repo, &NoRepoInCwdError>, |
|
773 | repo: Result<&Repo, &NoRepoInCwdError>, | |
@@ -740,6 +784,7 b' fn check_unsupported(' | |||||
740 | if repo.has_subrepos()? { |
|
784 | if repo.has_subrepos()? { | |
741 | Err(CommandError::unsupported("sub-repositories"))? |
|
785 | Err(CommandError::unsupported("sub-repositories"))? | |
742 | } |
|
786 | } | |
|
787 | check_auto_upgrade(config, repo.requirements())?; | |||
743 | } |
|
788 | } | |
744 |
|
789 | |||
745 | if config.has_non_empty_section(b"encode") { |
|
790 | if config.has_non_empty_section(b"encode") { |
General Comments 0
You need to be logged in to leave comments.
Login now