##// END OF EJS Templates
rhg: fix bugs around [use-dirstate-tracked-hint] and repo auto-upgrade...
Arseniy Alekseyev -
r50395:3a538710 stable
parent child Browse files
Show More
@@ -1,172 +1,173 b''
1 use crate::errors::{HgError, HgResultExt};
1 use crate::errors::{HgError, HgResultExt};
2 use crate::repo::Repo;
2 use crate::repo::Repo;
3 use crate::utils::join_display;
3 use crate::utils::join_display;
4 use crate::vfs::Vfs;
4 use crate::vfs::Vfs;
5 use std::collections::HashSet;
5 use std::collections::HashSet;
6
6
7 fn parse(bytes: &[u8]) -> Result<HashSet<String>, HgError> {
7 fn parse(bytes: &[u8]) -> Result<HashSet<String>, HgError> {
8 // The Python code reading this file uses `str.splitlines`
8 // The Python code reading this file uses `str.splitlines`
9 // which looks for a number of line separators (even including a couple of
9 // which looks for a number of line separators (even including a couple of
10 // non-ASCII ones), but Python code writing it always uses `\n`.
10 // non-ASCII ones), but Python code writing it always uses `\n`.
11 let lines = bytes.split(|&byte| byte == b'\n');
11 let lines = bytes.split(|&byte| byte == b'\n');
12
12
13 lines
13 lines
14 .filter(|line| !line.is_empty())
14 .filter(|line| !line.is_empty())
15 .map(|line| {
15 .map(|line| {
16 // Python uses Unicode `str.isalnum` but feature names are all
16 // Python uses Unicode `str.isalnum` but feature names are all
17 // ASCII
17 // ASCII
18 if line[0].is_ascii_alphanumeric() && line.is_ascii() {
18 if line[0].is_ascii_alphanumeric() && line.is_ascii() {
19 Ok(String::from_utf8(line.into()).unwrap())
19 Ok(String::from_utf8(line.into()).unwrap())
20 } else {
20 } else {
21 Err(HgError::corrupted("parse error in 'requires' file"))
21 Err(HgError::corrupted("parse error in 'requires' file"))
22 }
22 }
23 })
23 })
24 .collect()
24 .collect()
25 }
25 }
26
26
27 pub(crate) fn load(hg_vfs: Vfs) -> Result<HashSet<String>, HgError> {
27 pub(crate) fn load(hg_vfs: Vfs) -> Result<HashSet<String>, HgError> {
28 parse(&hg_vfs.read("requires")?)
28 parse(&hg_vfs.read("requires")?)
29 }
29 }
30
30
31 pub(crate) fn load_if_exists(hg_vfs: Vfs) -> Result<HashSet<String>, HgError> {
31 pub(crate) fn load_if_exists(hg_vfs: Vfs) -> Result<HashSet<String>, HgError> {
32 if let Some(bytes) = hg_vfs.read("requires").io_not_found_as_none()? {
32 if let Some(bytes) = hg_vfs.read("requires").io_not_found_as_none()? {
33 parse(&bytes)
33 parse(&bytes)
34 } else {
34 } else {
35 // Treat a missing file the same as an empty file.
35 // Treat a missing file the same as an empty file.
36 // From `mercurial/localrepo.py`:
36 // From `mercurial/localrepo.py`:
37 // > requires file contains a newline-delimited list of
37 // > requires file contains a newline-delimited list of
38 // > features/capabilities the opener (us) must have in order to use
38 // > features/capabilities the opener (us) must have in order to use
39 // > the repository. This file was introduced in Mercurial 0.9.2,
39 // > the repository. This file was introduced in Mercurial 0.9.2,
40 // > which means very old repositories may not have one. We assume
40 // > which means very old repositories may not have one. We assume
41 // > a missing file translates to no requirements.
41 // > a missing file translates to no requirements.
42 Ok(HashSet::new())
42 Ok(HashSet::new())
43 }
43 }
44 }
44 }
45
45
46 pub(crate) fn check(repo: &Repo) -> Result<(), HgError> {
46 pub(crate) fn check(repo: &Repo) -> Result<(), HgError> {
47 let unknown: Vec<_> = repo
47 let unknown: Vec<_> = repo
48 .requirements()
48 .requirements()
49 .iter()
49 .iter()
50 .map(String::as_str)
50 .map(String::as_str)
51 // .filter(|feature| !ALL_SUPPORTED.contains(feature.as_str()))
51 // .filter(|feature| !ALL_SUPPORTED.contains(feature.as_str()))
52 .filter(|feature| {
52 .filter(|feature| {
53 !REQUIRED.contains(feature) && !SUPPORTED.contains(feature)
53 !REQUIRED.contains(feature) && !SUPPORTED.contains(feature)
54 })
54 })
55 .collect();
55 .collect();
56 if !unknown.is_empty() {
56 if !unknown.is_empty() {
57 return Err(HgError::unsupported(format!(
57 return Err(HgError::unsupported(format!(
58 "repository requires feature unknown to this Mercurial: {}",
58 "repository requires feature unknown to this Mercurial: {}",
59 join_display(&unknown, ", ")
59 join_display(&unknown, ", ")
60 )));
60 )));
61 }
61 }
62 let missing: Vec<_> = REQUIRED
62 let missing: Vec<_> = REQUIRED
63 .iter()
63 .iter()
64 .filter(|&&feature| !repo.requirements().contains(feature))
64 .filter(|&&feature| !repo.requirements().contains(feature))
65 .collect();
65 .collect();
66 if !missing.is_empty() {
66 if !missing.is_empty() {
67 return Err(HgError::unsupported(format!(
67 return Err(HgError::unsupported(format!(
68 "repository is missing feature required by this Mercurial: {}",
68 "repository is missing feature required by this Mercurial: {}",
69 join_display(&missing, ", ")
69 join_display(&missing, ", ")
70 )));
70 )));
71 }
71 }
72 Ok(())
72 Ok(())
73 }
73 }
74
74
75 /// rhg does not support repositories that are *missing* any of these features
75 /// rhg does not support repositories that are *missing* any of these features
76 const REQUIRED: &[&str] = &["revlogv1", "store", "fncache", "dotencode"];
76 const REQUIRED: &[&str] = &["revlogv1", "store", "fncache", "dotencode"];
77
77
78 /// rhg supports repository with or without these
78 /// rhg supports repository with or without these
79 const SUPPORTED: &[&str] = &[
79 const SUPPORTED: &[&str] = &[
80 "generaldelta",
80 "generaldelta",
81 SHARED_REQUIREMENT,
81 SHARED_REQUIREMENT,
82 SHARESAFE_REQUIREMENT,
82 SHARESAFE_REQUIREMENT,
83 SPARSEREVLOG_REQUIREMENT,
83 SPARSEREVLOG_REQUIREMENT,
84 RELATIVE_SHARED_REQUIREMENT,
84 RELATIVE_SHARED_REQUIREMENT,
85 REVLOG_COMPRESSION_ZSTD,
85 REVLOG_COMPRESSION_ZSTD,
86 DIRSTATE_V2_REQUIREMENT,
86 DIRSTATE_V2_REQUIREMENT,
87 DIRSTATE_TRACKED_HINT_V1,
87 // As of this writing everything rhg does is read-only.
88 // As of this writing everything rhg does is read-only.
88 // When it starts writing to the repository, it’ll need to either keep the
89 // When it starts writing to the repository, it’ll need to either keep the
89 // persistent nodemap up to date or remove this entry:
90 // persistent nodemap up to date or remove this entry:
90 NODEMAP_REQUIREMENT,
91 NODEMAP_REQUIREMENT,
91 // Not all commands support `sparse` and `narrow`. The commands that do
92 // Not all commands support `sparse` and `narrow`. The commands that do
92 // not should opt out by checking `has_sparse` and `has_narrow`.
93 // not should opt out by checking `has_sparse` and `has_narrow`.
93 SPARSE_REQUIREMENT,
94 SPARSE_REQUIREMENT,
94 NARROW_REQUIREMENT,
95 NARROW_REQUIREMENT,
95 // rhg doesn't care about bookmarks at all yet
96 // rhg doesn't care about bookmarks at all yet
96 BOOKMARKS_IN_STORE_REQUIREMENT,
97 BOOKMARKS_IN_STORE_REQUIREMENT,
97 ];
98 ];
98
99
99 // Copied from mercurial/requirements.py:
100 // Copied from mercurial/requirements.py:
100
101
101 pub const DIRSTATE_V2_REQUIREMENT: &str = "dirstate-v2";
102 pub const DIRSTATE_V2_REQUIREMENT: &str = "dirstate-v2";
102
103
103 /// A repository that uses the tracked hint dirstate file
104 /// A repository that uses the tracked hint dirstate file
104 #[allow(unused)]
105 #[allow(unused)]
105 pub const DIRSTATE_TRACKED_HINT_V1: &str = "dirstate-tracked-key-v1";
106 pub const DIRSTATE_TRACKED_HINT_V1: &str = "dirstate-tracked-key-v1";
106
107
107 /// When narrowing is finalized and no longer subject to format changes,
108 /// When narrowing is finalized and no longer subject to format changes,
108 /// we should move this to just "narrow" or similar.
109 /// we should move this to just "narrow" or similar.
109 #[allow(unused)]
110 #[allow(unused)]
110 pub const NARROW_REQUIREMENT: &str = "narrowhg-experimental";
111 pub const NARROW_REQUIREMENT: &str = "narrowhg-experimental";
111
112
112 /// Bookmarks must be stored in the `store` part of the repository and will be
113 /// Bookmarks must be stored in the `store` part of the repository and will be
113 /// share accross shares
114 /// share accross shares
114 #[allow(unused)]
115 #[allow(unused)]
115 pub const BOOKMARKS_IN_STORE_REQUIREMENT: &str = "bookmarksinstore";
116 pub const BOOKMARKS_IN_STORE_REQUIREMENT: &str = "bookmarksinstore";
116
117
117 /// Enables sparse working directory usage
118 /// Enables sparse working directory usage
118 #[allow(unused)]
119 #[allow(unused)]
119 pub const SPARSE_REQUIREMENT: &str = "exp-sparse";
120 pub const SPARSE_REQUIREMENT: &str = "exp-sparse";
120
121
121 /// Enables the internal phase which is used to hide changesets instead
122 /// Enables the internal phase which is used to hide changesets instead
122 /// of stripping them
123 /// of stripping them
123 #[allow(unused)]
124 #[allow(unused)]
124 pub const INTERNAL_PHASE_REQUIREMENT: &str = "internal-phase";
125 pub const INTERNAL_PHASE_REQUIREMENT: &str = "internal-phase";
125
126
126 /// Stores manifest in Tree structure
127 /// Stores manifest in Tree structure
127 #[allow(unused)]
128 #[allow(unused)]
128 pub const TREEMANIFEST_REQUIREMENT: &str = "treemanifest";
129 pub const TREEMANIFEST_REQUIREMENT: &str = "treemanifest";
129
130
130 /// Increment the sub-version when the revlog v2 format changes to lock out old
131 /// Increment the sub-version when the revlog v2 format changes to lock out old
131 /// clients.
132 /// clients.
132 #[allow(unused)]
133 #[allow(unused)]
133 pub const REVLOGV2_REQUIREMENT: &str = "exp-revlogv2.1";
134 pub const REVLOGV2_REQUIREMENT: &str = "exp-revlogv2.1";
134
135
135 /// A repository with the sparserevlog feature will have delta chains that
136 /// A repository with the sparserevlog feature will have delta chains that
136 /// can spread over a larger span. Sparse reading cuts these large spans into
137 /// can spread over a larger span. Sparse reading cuts these large spans into
137 /// pieces, so that each piece isn't too big.
138 /// pieces, so that each piece isn't too big.
138 /// Without the sparserevlog capability, reading from the repository could use
139 /// Without the sparserevlog capability, reading from the repository could use
139 /// huge amounts of memory, because the whole span would be read at once,
140 /// huge amounts of memory, because the whole span would be read at once,
140 /// including all the intermediate revisions that aren't pertinent for the
141 /// including all the intermediate revisions that aren't pertinent for the
141 /// chain. This is why once a repository has enabled sparse-read, it becomes
142 /// chain. This is why once a repository has enabled sparse-read, it becomes
142 /// required.
143 /// required.
143 #[allow(unused)]
144 #[allow(unused)]
144 pub const SPARSEREVLOG_REQUIREMENT: &str = "sparserevlog";
145 pub const SPARSEREVLOG_REQUIREMENT: &str = "sparserevlog";
145
146
146 /// A repository with the the copies-sidedata-changeset requirement will store
147 /// A repository with the the copies-sidedata-changeset requirement will store
147 /// copies related information in changeset's sidedata.
148 /// copies related information in changeset's sidedata.
148 #[allow(unused)]
149 #[allow(unused)]
149 pub const COPIESSDC_REQUIREMENT: &str = "exp-copies-sidedata-changeset";
150 pub const COPIESSDC_REQUIREMENT: &str = "exp-copies-sidedata-changeset";
150
151
151 /// The repository use persistent nodemap for the changelog and the manifest.
152 /// The repository use persistent nodemap for the changelog and the manifest.
152 #[allow(unused)]
153 #[allow(unused)]
153 pub const NODEMAP_REQUIREMENT: &str = "persistent-nodemap";
154 pub const NODEMAP_REQUIREMENT: &str = "persistent-nodemap";
154
155
155 /// Denotes that the current repository is a share
156 /// Denotes that the current repository is a share
156 #[allow(unused)]
157 #[allow(unused)]
157 pub const SHARED_REQUIREMENT: &str = "shared";
158 pub const SHARED_REQUIREMENT: &str = "shared";
158
159
159 /// Denotes that current repository is a share and the shared source path is
160 /// Denotes that current repository is a share and the shared source path is
160 /// relative to the current repository root path
161 /// relative to the current repository root path
161 #[allow(unused)]
162 #[allow(unused)]
162 pub const RELATIVE_SHARED_REQUIREMENT: &str = "relshared";
163 pub const RELATIVE_SHARED_REQUIREMENT: &str = "relshared";
163
164
164 /// A repository with share implemented safely. The repository has different
165 /// A repository with share implemented safely. The repository has different
165 /// store and working copy requirements i.e. both `.hg/requires` and
166 /// store and working copy requirements i.e. both `.hg/requires` and
166 /// `.hg/store/requires` are present.
167 /// `.hg/store/requires` are present.
167 #[allow(unused)]
168 #[allow(unused)]
168 pub const SHARESAFE_REQUIREMENT: &str = "share-safe";
169 pub const SHARESAFE_REQUIREMENT: &str = "share-safe";
169
170
170 /// A repository that use zstd compression inside its revlog
171 /// A repository that use zstd compression inside its revlog
171 #[allow(unused)]
172 #[allow(unused)]
172 pub const REVLOG_COMPRESSION_ZSTD: &str = "revlog-compression-zstd";
173 pub const REVLOG_COMPRESSION_ZSTD: &str = "revlog-compression-zstd";
@@ -1,809 +1,809 b''
1 extern crate log;
1 extern crate log;
2 use crate::error::CommandError;
2 use crate::error::CommandError;
3 use crate::ui::{local_to_utf8, Ui};
3 use crate::ui::{local_to_utf8, Ui};
4 use clap::App;
4 use clap::App;
5 use clap::AppSettings;
5 use clap::AppSettings;
6 use clap::Arg;
6 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::repo::{Repo, RepoError};
10 use hg::repo::{Repo, RepoError};
11 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};
12 use hg::utils::SliceExt;
12 use hg::utils::SliceExt;
13 use hg::{exit_codes, requirements};
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;
17 use std::path::PathBuf;
17 use std::path::PathBuf;
18 use std::process::Command;
18 use std::process::Command;
19
19
20 mod blackbox;
20 mod blackbox;
21 mod color;
21 mod color;
22 mod error;
22 mod error;
23 mod ui;
23 mod ui;
24 pub mod utils {
24 pub mod utils {
25 pub mod path_utils;
25 pub mod path_utils;
26 }
26 }
27
27
28 fn main_with_result(
28 fn main_with_result(
29 argv: Vec<OsString>,
29 argv: Vec<OsString>,
30 process_start_time: &blackbox::ProcessStartTime,
30 process_start_time: &blackbox::ProcessStartTime,
31 ui: &ui::Ui,
31 ui: &ui::Ui,
32 repo: Result<&Repo, &NoRepoInCwdError>,
32 repo: Result<&Repo, &NoRepoInCwdError>,
33 config: &Config,
33 config: &Config,
34 ) -> Result<(), CommandError> {
34 ) -> Result<(), CommandError> {
35 check_unsupported(config, repo)?;
35 check_unsupported(config, repo)?;
36
36
37 let app = App::new("rhg")
37 let app = App::new("rhg")
38 .global_setting(AppSettings::AllowInvalidUtf8)
38 .global_setting(AppSettings::AllowInvalidUtf8)
39 .global_setting(AppSettings::DisableVersion)
39 .global_setting(AppSettings::DisableVersion)
40 .setting(AppSettings::SubcommandRequired)
40 .setting(AppSettings::SubcommandRequired)
41 .setting(AppSettings::VersionlessSubcommands)
41 .setting(AppSettings::VersionlessSubcommands)
42 .arg(
42 .arg(
43 Arg::with_name("repository")
43 Arg::with_name("repository")
44 .help("repository root directory")
44 .help("repository root directory")
45 .short("-R")
45 .short("-R")
46 .long("--repository")
46 .long("--repository")
47 .value_name("REPO")
47 .value_name("REPO")
48 .takes_value(true)
48 .takes_value(true)
49 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
49 // Both ok: `hg -R ./foo log` or `hg log -R ./foo`
50 .global(true),
50 .global(true),
51 )
51 )
52 .arg(
52 .arg(
53 Arg::with_name("config")
53 Arg::with_name("config")
54 .help("set/override config option (use 'section.name=value')")
54 .help("set/override config option (use 'section.name=value')")
55 .long("--config")
55 .long("--config")
56 .value_name("CONFIG")
56 .value_name("CONFIG")
57 .takes_value(true)
57 .takes_value(true)
58 .global(true)
58 .global(true)
59 // Ok: `--config section.key1=val --config section.key2=val2`
59 // Ok: `--config section.key1=val --config section.key2=val2`
60 .multiple(true)
60 .multiple(true)
61 // Not ok: `--config section.key1=val section.key2=val2`
61 // Not ok: `--config section.key1=val section.key2=val2`
62 .number_of_values(1),
62 .number_of_values(1),
63 )
63 )
64 .arg(
64 .arg(
65 Arg::with_name("cwd")
65 Arg::with_name("cwd")
66 .help("change working directory")
66 .help("change working directory")
67 .long("--cwd")
67 .long("--cwd")
68 .value_name("DIR")
68 .value_name("DIR")
69 .takes_value(true)
69 .takes_value(true)
70 .global(true),
70 .global(true),
71 )
71 )
72 .arg(
72 .arg(
73 Arg::with_name("color")
73 Arg::with_name("color")
74 .help("when to colorize (boolean, always, auto, never, or debug)")
74 .help("when to colorize (boolean, always, auto, never, or debug)")
75 .long("--color")
75 .long("--color")
76 .value_name("TYPE")
76 .value_name("TYPE")
77 .takes_value(true)
77 .takes_value(true)
78 .global(true),
78 .global(true),
79 )
79 )
80 .version("0.0.1");
80 .version("0.0.1");
81 let app = add_subcommand_args(app);
81 let app = add_subcommand_args(app);
82
82
83 let matches = app.clone().get_matches_from_safe(argv.iter())?;
83 let matches = app.clone().get_matches_from_safe(argv.iter())?;
84
84
85 let (subcommand_name, subcommand_matches) = matches.subcommand();
85 let (subcommand_name, subcommand_matches) = matches.subcommand();
86
86
87 // Mercurial allows users to define "defaults" for commands, fallback
87 // Mercurial allows users to define "defaults" for commands, fallback
88 // if a default is detected for the current command
88 // if a default is detected for the current command
89 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
89 let defaults = config.get_str(b"defaults", subcommand_name.as_bytes());
90 if defaults?.is_some() {
90 if defaults?.is_some() {
91 let msg = "`defaults` config set";
91 let msg = "`defaults` config set";
92 return Err(CommandError::unsupported(msg));
92 return Err(CommandError::unsupported(msg));
93 }
93 }
94
94
95 for prefix in ["pre", "post", "fail"].iter() {
95 for prefix in ["pre", "post", "fail"].iter() {
96 // Mercurial allows users to define generic hooks for commands,
96 // Mercurial allows users to define generic hooks for commands,
97 // fallback if any are detected
97 // fallback if any are detected
98 let item = format!("{}-{}", prefix, subcommand_name);
98 let item = format!("{}-{}", prefix, subcommand_name);
99 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
99 let hook_for_command = config.get_str(b"hooks", item.as_bytes())?;
100 if hook_for_command.is_some() {
100 if hook_for_command.is_some() {
101 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
101 let msg = format!("{}-{} hook defined", prefix, subcommand_name);
102 return Err(CommandError::unsupported(msg));
102 return Err(CommandError::unsupported(msg));
103 }
103 }
104 }
104 }
105 let run = subcommand_run_fn(subcommand_name)
105 let run = subcommand_run_fn(subcommand_name)
106 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
106 .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired");
107 let subcommand_args = subcommand_matches
107 let subcommand_args = subcommand_matches
108 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
108 .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired");
109
109
110 let invocation = CliInvocation {
110 let invocation = CliInvocation {
111 ui,
111 ui,
112 subcommand_args,
112 subcommand_args,
113 config,
113 config,
114 repo,
114 repo,
115 };
115 };
116
116
117 if let Ok(repo) = repo {
117 if let Ok(repo) = repo {
118 // We don't support subrepos, fallback if the subrepos file is present
118 // We don't support subrepos, fallback if the subrepos file is present
119 if repo.working_directory_vfs().join(".hgsub").exists() {
119 if repo.working_directory_vfs().join(".hgsub").exists() {
120 let msg = "subrepos (.hgsub is present)";
120 let msg = "subrepos (.hgsub is present)";
121 return Err(CommandError::unsupported(msg));
121 return Err(CommandError::unsupported(msg));
122 }
122 }
123 }
123 }
124
124
125 if config.is_extension_enabled(b"blackbox") {
125 if config.is_extension_enabled(b"blackbox") {
126 let blackbox =
126 let blackbox =
127 blackbox::Blackbox::new(&invocation, process_start_time)?;
127 blackbox::Blackbox::new(&invocation, process_start_time)?;
128 blackbox.log_command_start(argv.iter());
128 blackbox.log_command_start(argv.iter());
129 let result = run(&invocation);
129 let result = run(&invocation);
130 blackbox.log_command_end(
130 blackbox.log_command_end(
131 argv.iter(),
131 argv.iter(),
132 exit_code(
132 exit_code(
133 &result,
133 &result,
134 // TODO: show a warning or combine with original error if
134 // TODO: show a warning or combine with original error if
135 // `get_bool` returns an error
135 // `get_bool` returns an error
136 config
136 config
137 .get_bool(b"ui", b"detailed-exit-code")
137 .get_bool(b"ui", b"detailed-exit-code")
138 .unwrap_or(false),
138 .unwrap_or(false),
139 ),
139 ),
140 );
140 );
141 result
141 result
142 } else {
142 } else {
143 run(&invocation)
143 run(&invocation)
144 }
144 }
145 }
145 }
146
146
147 fn rhg_main(argv: Vec<OsString>) -> ! {
147 fn rhg_main(argv: Vec<OsString>) -> ! {
148 // Run this first, before we find out if the blackbox extension is even
148 // Run this first, before we find out if the blackbox extension is even
149 // enabled, in order to include everything in-between in the duration
149 // enabled, in order to include everything in-between in the duration
150 // measurements. Reading config files can be slow if they’re on NFS.
150 // measurements. Reading config files can be slow if they’re on NFS.
151 let process_start_time = blackbox::ProcessStartTime::now();
151 let process_start_time = blackbox::ProcessStartTime::now();
152
152
153 env_logger::init();
153 env_logger::init();
154
154
155 let early_args = EarlyArgs::parse(&argv);
155 let early_args = EarlyArgs::parse(&argv);
156
156
157 let initial_current_dir = early_args.cwd.map(|cwd| {
157 let initial_current_dir = early_args.cwd.map(|cwd| {
158 let cwd = get_path_from_bytes(&cwd);
158 let cwd = get_path_from_bytes(&cwd);
159 std::env::current_dir()
159 std::env::current_dir()
160 .and_then(|initial| {
160 .and_then(|initial| {
161 std::env::set_current_dir(cwd)?;
161 std::env::set_current_dir(cwd)?;
162 Ok(initial)
162 Ok(initial)
163 })
163 })
164 .unwrap_or_else(|error| {
164 .unwrap_or_else(|error| {
165 exit(
165 exit(
166 &argv,
166 &argv,
167 &None,
167 &None,
168 &Ui::new_infallible(&Config::empty()),
168 &Ui::new_infallible(&Config::empty()),
169 OnUnsupported::Abort,
169 OnUnsupported::Abort,
170 Err(CommandError::abort(format!(
170 Err(CommandError::abort(format!(
171 "abort: {}: '{}'",
171 "abort: {}: '{}'",
172 error,
172 error,
173 cwd.display()
173 cwd.display()
174 ))),
174 ))),
175 false,
175 false,
176 )
176 )
177 })
177 })
178 });
178 });
179
179
180 let mut non_repo_config =
180 let mut non_repo_config =
181 Config::load_non_repo().unwrap_or_else(|error| {
181 Config::load_non_repo().unwrap_or_else(|error| {
182 // Normally this is decided based on config, but we don’t have that
182 // Normally this is decided based on config, but we don’t have that
183 // available. As of this writing config loading never returns an
183 // available. As of this writing config loading never returns an
184 // "unsupported" error but that is not enforced by the type system.
184 // "unsupported" error but that is not enforced by the type system.
185 let on_unsupported = OnUnsupported::Abort;
185 let on_unsupported = OnUnsupported::Abort;
186
186
187 exit(
187 exit(
188 &argv,
188 &argv,
189 &initial_current_dir,
189 &initial_current_dir,
190 &Ui::new_infallible(&Config::empty()),
190 &Ui::new_infallible(&Config::empty()),
191 on_unsupported,
191 on_unsupported,
192 Err(error.into()),
192 Err(error.into()),
193 false,
193 false,
194 )
194 )
195 });
195 });
196
196
197 non_repo_config
197 non_repo_config
198 .load_cli_args(early_args.config, early_args.color)
198 .load_cli_args(early_args.config, early_args.color)
199 .unwrap_or_else(|error| {
199 .unwrap_or_else(|error| {
200 exit(
200 exit(
201 &argv,
201 &argv,
202 &initial_current_dir,
202 &initial_current_dir,
203 &Ui::new_infallible(&non_repo_config),
203 &Ui::new_infallible(&non_repo_config),
204 OnUnsupported::from_config(&non_repo_config),
204 OnUnsupported::from_config(&non_repo_config),
205 Err(error.into()),
205 Err(error.into()),
206 non_repo_config
206 non_repo_config
207 .get_bool(b"ui", b"detailed-exit-code")
207 .get_bool(b"ui", b"detailed-exit-code")
208 .unwrap_or(false),
208 .unwrap_or(false),
209 )
209 )
210 });
210 });
211
211
212 if let Some(repo_path_bytes) = &early_args.repo {
212 if let Some(repo_path_bytes) = &early_args.repo {
213 lazy_static::lazy_static! {
213 lazy_static::lazy_static! {
214 static ref SCHEME_RE: regex::bytes::Regex =
214 static ref SCHEME_RE: regex::bytes::Regex =
215 // Same as `_matchscheme` in `mercurial/util.py`
215 // Same as `_matchscheme` in `mercurial/util.py`
216 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
216 regex::bytes::Regex::new("^[a-zA-Z0-9+.\\-]+:").unwrap();
217 }
217 }
218 if SCHEME_RE.is_match(&repo_path_bytes) {
218 if SCHEME_RE.is_match(&repo_path_bytes) {
219 exit(
219 exit(
220 &argv,
220 &argv,
221 &initial_current_dir,
221 &initial_current_dir,
222 &Ui::new_infallible(&non_repo_config),
222 &Ui::new_infallible(&non_repo_config),
223 OnUnsupported::from_config(&non_repo_config),
223 OnUnsupported::from_config(&non_repo_config),
224 Err(CommandError::UnsupportedFeature {
224 Err(CommandError::UnsupportedFeature {
225 message: format_bytes!(
225 message: format_bytes!(
226 b"URL-like --repository {}",
226 b"URL-like --repository {}",
227 repo_path_bytes
227 repo_path_bytes
228 ),
228 ),
229 }),
229 }),
230 // TODO: show a warning or combine with original error if
230 // TODO: show a warning or combine with original error if
231 // `get_bool` returns an error
231 // `get_bool` returns an error
232 non_repo_config
232 non_repo_config
233 .get_bool(b"ui", b"detailed-exit-code")
233 .get_bool(b"ui", b"detailed-exit-code")
234 .unwrap_or(false),
234 .unwrap_or(false),
235 )
235 )
236 }
236 }
237 }
237 }
238 let repo_arg = early_args.repo.unwrap_or(Vec::new());
238 let repo_arg = early_args.repo.unwrap_or(Vec::new());
239 let repo_path: Option<PathBuf> = {
239 let repo_path: Option<PathBuf> = {
240 if repo_arg.is_empty() {
240 if repo_arg.is_empty() {
241 None
241 None
242 } else {
242 } else {
243 let local_config = {
243 let local_config = {
244 if std::env::var_os("HGRCSKIPREPO").is_none() {
244 if std::env::var_os("HGRCSKIPREPO").is_none() {
245 // TODO: handle errors from find_repo_root
245 // TODO: handle errors from find_repo_root
246 if let Ok(current_dir_path) = Repo::find_repo_root() {
246 if let Ok(current_dir_path) = Repo::find_repo_root() {
247 let config_files = vec![
247 let config_files = vec![
248 ConfigSource::AbsPath(
248 ConfigSource::AbsPath(
249 current_dir_path.join(".hg/hgrc"),
249 current_dir_path.join(".hg/hgrc"),
250 ),
250 ),
251 ConfigSource::AbsPath(
251 ConfigSource::AbsPath(
252 current_dir_path.join(".hg/hgrc-not-shared"),
252 current_dir_path.join(".hg/hgrc-not-shared"),
253 ),
253 ),
254 ];
254 ];
255 // TODO: handle errors from
255 // TODO: handle errors from
256 // `load_from_explicit_sources`
256 // `load_from_explicit_sources`
257 Config::load_from_explicit_sources(config_files).ok()
257 Config::load_from_explicit_sources(config_files).ok()
258 } else {
258 } else {
259 None
259 None
260 }
260 }
261 } else {
261 } else {
262 None
262 None
263 }
263 }
264 };
264 };
265
265
266 let non_repo_config_val = {
266 let non_repo_config_val = {
267 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
267 let non_repo_val = non_repo_config.get(b"paths", &repo_arg);
268 match &non_repo_val {
268 match &non_repo_val {
269 Some(val) if val.len() > 0 => home::home_dir()
269 Some(val) if val.len() > 0 => home::home_dir()
270 .unwrap_or_else(|| PathBuf::from("~"))
270 .unwrap_or_else(|| PathBuf::from("~"))
271 .join(get_path_from_bytes(val))
271 .join(get_path_from_bytes(val))
272 .canonicalize()
272 .canonicalize()
273 // TODO: handle error and make it similar to python
273 // TODO: handle error and make it similar to python
274 // implementation maybe?
274 // implementation maybe?
275 .ok(),
275 .ok(),
276 _ => None,
276 _ => None,
277 }
277 }
278 };
278 };
279
279
280 let config_val = match &local_config {
280 let config_val = match &local_config {
281 None => non_repo_config_val,
281 None => non_repo_config_val,
282 Some(val) => {
282 Some(val) => {
283 let local_config_val = val.get(b"paths", &repo_arg);
283 let local_config_val = val.get(b"paths", &repo_arg);
284 match &local_config_val {
284 match &local_config_val {
285 Some(val) if val.len() > 0 => {
285 Some(val) if val.len() > 0 => {
286 // presence of a local_config assures that
286 // presence of a local_config assures that
287 // current_dir
287 // current_dir
288 // wont result in an Error
288 // wont result in an Error
289 let canpath = hg::utils::current_dir()
289 let canpath = hg::utils::current_dir()
290 .unwrap()
290 .unwrap()
291 .join(get_path_from_bytes(val))
291 .join(get_path_from_bytes(val))
292 .canonicalize();
292 .canonicalize();
293 canpath.ok().or(non_repo_config_val)
293 canpath.ok().or(non_repo_config_val)
294 }
294 }
295 _ => non_repo_config_val,
295 _ => non_repo_config_val,
296 }
296 }
297 }
297 }
298 };
298 };
299 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
299 config_val.or(Some(get_path_from_bytes(&repo_arg).to_path_buf()))
300 }
300 }
301 };
301 };
302
302
303 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
303 let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
304 {
304 {
305 Ok(repo) => Ok(repo),
305 Ok(repo) => Ok(repo),
306 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
306 Err(RepoError::NotFound { at }) if repo_path.is_none() => {
307 // Not finding a repo is not fatal yet, if `-R` was not given
307 // Not finding a repo is not fatal yet, if `-R` was not given
308 Err(NoRepoInCwdError { cwd: at })
308 Err(NoRepoInCwdError { cwd: at })
309 }
309 }
310 Err(error) => exit(
310 Err(error) => exit(
311 &argv,
311 &argv,
312 &initial_current_dir,
312 &initial_current_dir,
313 &Ui::new_infallible(&non_repo_config),
313 &Ui::new_infallible(&non_repo_config),
314 OnUnsupported::from_config(&non_repo_config),
314 OnUnsupported::from_config(&non_repo_config),
315 Err(error.into()),
315 Err(error.into()),
316 // TODO: show a warning or combine with original error if
316 // TODO: show a warning or combine with original error if
317 // `get_bool` returns an error
317 // `get_bool` returns an error
318 non_repo_config
318 non_repo_config
319 .get_bool(b"ui", b"detailed-exit-code")
319 .get_bool(b"ui", b"detailed-exit-code")
320 .unwrap_or(false),
320 .unwrap_or(false),
321 ),
321 ),
322 };
322 };
323
323
324 let config = if let Ok(repo) = &repo_result {
324 let config = if let Ok(repo) = &repo_result {
325 repo.config()
325 repo.config()
326 } else {
326 } else {
327 &non_repo_config
327 &non_repo_config
328 };
328 };
329 let ui = Ui::new(&config).unwrap_or_else(|error| {
329 let ui = Ui::new(&config).unwrap_or_else(|error| {
330 exit(
330 exit(
331 &argv,
331 &argv,
332 &initial_current_dir,
332 &initial_current_dir,
333 &Ui::new_infallible(&config),
333 &Ui::new_infallible(&config),
334 OnUnsupported::from_config(&config),
334 OnUnsupported::from_config(&config),
335 Err(error.into()),
335 Err(error.into()),
336 config
336 config
337 .get_bool(b"ui", b"detailed-exit-code")
337 .get_bool(b"ui", b"detailed-exit-code")
338 .unwrap_or(false),
338 .unwrap_or(false),
339 )
339 )
340 });
340 });
341 let on_unsupported = OnUnsupported::from_config(config);
341 let on_unsupported = OnUnsupported::from_config(config);
342
342
343 let result = main_with_result(
343 let result = main_with_result(
344 argv.iter().map(|s| s.to_owned()).collect(),
344 argv.iter().map(|s| s.to_owned()).collect(),
345 &process_start_time,
345 &process_start_time,
346 &ui,
346 &ui,
347 repo_result.as_ref(),
347 repo_result.as_ref(),
348 config,
348 config,
349 );
349 );
350 exit(
350 exit(
351 &argv,
351 &argv,
352 &initial_current_dir,
352 &initial_current_dir,
353 &ui,
353 &ui,
354 on_unsupported,
354 on_unsupported,
355 result,
355 result,
356 // TODO: show a warning or combine with original error if `get_bool`
356 // TODO: show a warning or combine with original error if `get_bool`
357 // returns an error
357 // returns an error
358 config
358 config
359 .get_bool(b"ui", b"detailed-exit-code")
359 .get_bool(b"ui", b"detailed-exit-code")
360 .unwrap_or(false),
360 .unwrap_or(false),
361 )
361 )
362 }
362 }
363
363
364 fn main() -> ! {
364 fn main() -> ! {
365 rhg_main(std::env::args_os().collect())
365 rhg_main(std::env::args_os().collect())
366 }
366 }
367
367
368 fn exit_code(
368 fn exit_code(
369 result: &Result<(), CommandError>,
369 result: &Result<(), CommandError>,
370 use_detailed_exit_code: bool,
370 use_detailed_exit_code: bool,
371 ) -> i32 {
371 ) -> i32 {
372 match result {
372 match result {
373 Ok(()) => exit_codes::OK,
373 Ok(()) => exit_codes::OK,
374 Err(CommandError::Abort {
374 Err(CommandError::Abort {
375 message: _,
375 message: _,
376 detailed_exit_code,
376 detailed_exit_code,
377 }) => {
377 }) => {
378 if use_detailed_exit_code {
378 if use_detailed_exit_code {
379 *detailed_exit_code
379 *detailed_exit_code
380 } else {
380 } else {
381 exit_codes::ABORT
381 exit_codes::ABORT
382 }
382 }
383 }
383 }
384 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
384 Err(CommandError::Unsuccessful) => exit_codes::UNSUCCESSFUL,
385 // Exit with a specific code and no error message to let a potential
385 // Exit with a specific code and no error message to let a potential
386 // wrapper script fallback to Python-based Mercurial.
386 // wrapper script fallback to Python-based Mercurial.
387 Err(CommandError::UnsupportedFeature { .. }) => {
387 Err(CommandError::UnsupportedFeature { .. }) => {
388 exit_codes::UNIMPLEMENTED
388 exit_codes::UNIMPLEMENTED
389 }
389 }
390 Err(CommandError::InvalidFallback { .. }) => {
390 Err(CommandError::InvalidFallback { .. }) => {
391 exit_codes::INVALID_FALLBACK
391 exit_codes::INVALID_FALLBACK
392 }
392 }
393 }
393 }
394 }
394 }
395
395
396 fn exit<'a>(
396 fn exit<'a>(
397 original_args: &'a [OsString],
397 original_args: &'a [OsString],
398 initial_current_dir: &Option<PathBuf>,
398 initial_current_dir: &Option<PathBuf>,
399 ui: &Ui,
399 ui: &Ui,
400 mut on_unsupported: OnUnsupported,
400 mut on_unsupported: OnUnsupported,
401 result: Result<(), CommandError>,
401 result: Result<(), CommandError>,
402 use_detailed_exit_code: bool,
402 use_detailed_exit_code: bool,
403 ) -> ! {
403 ) -> ! {
404 if let (
404 if let (
405 OnUnsupported::Fallback { executable },
405 OnUnsupported::Fallback { executable },
406 Err(CommandError::UnsupportedFeature { message }),
406 Err(CommandError::UnsupportedFeature { message }),
407 ) = (&on_unsupported, &result)
407 ) = (&on_unsupported, &result)
408 {
408 {
409 let mut args = original_args.iter();
409 let mut args = original_args.iter();
410 let executable = match executable {
410 let executable = match executable {
411 None => {
411 None => {
412 exit_no_fallback(
412 exit_no_fallback(
413 ui,
413 ui,
414 OnUnsupported::Abort,
414 OnUnsupported::Abort,
415 Err(CommandError::abort(
415 Err(CommandError::abort(
416 "abort: 'rhg.on-unsupported=fallback' without \
416 "abort: 'rhg.on-unsupported=fallback' without \
417 'rhg.fallback-executable' set.",
417 'rhg.fallback-executable' set.",
418 )),
418 )),
419 false,
419 false,
420 );
420 );
421 }
421 }
422 Some(executable) => executable,
422 Some(executable) => executable,
423 };
423 };
424 let executable_path = get_path_from_bytes(&executable);
424 let executable_path = get_path_from_bytes(&executable);
425 let this_executable = args.next().expect("exepcted argv[0] to exist");
425 let this_executable = args.next().expect("exepcted argv[0] to exist");
426 if executable_path == &PathBuf::from(this_executable) {
426 if executable_path == &PathBuf::from(this_executable) {
427 // Avoid spawning infinitely many processes until resource
427 // Avoid spawning infinitely many processes until resource
428 // exhaustion.
428 // exhaustion.
429 let _ = ui.write_stderr(&format_bytes!(
429 let _ = ui.write_stderr(&format_bytes!(
430 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
430 b"Blocking recursive fallback. The 'rhg.fallback-executable = {}' config \
431 points to `rhg` itself.\n",
431 points to `rhg` itself.\n",
432 executable
432 executable
433 ));
433 ));
434 on_unsupported = OnUnsupported::Abort
434 on_unsupported = OnUnsupported::Abort
435 } else {
435 } else {
436 log::debug!("falling back (see trace-level log)");
436 log::debug!("falling back (see trace-level log)");
437 log::trace!("{}", local_to_utf8(message));
437 log::trace!("{}", local_to_utf8(message));
438 if let Err(err) = which::which(executable_path) {
438 if let Err(err) = which::which(executable_path) {
439 exit_no_fallback(
439 exit_no_fallback(
440 ui,
440 ui,
441 OnUnsupported::Abort,
441 OnUnsupported::Abort,
442 Err(CommandError::InvalidFallback {
442 Err(CommandError::InvalidFallback {
443 path: executable.to_owned(),
443 path: executable.to_owned(),
444 err: err.to_string(),
444 err: err.to_string(),
445 }),
445 }),
446 use_detailed_exit_code,
446 use_detailed_exit_code,
447 )
447 )
448 }
448 }
449 // `args` is now `argv[1..]` since we’ve already consumed
449 // `args` is now `argv[1..]` since we’ve already consumed
450 // `argv[0]`
450 // `argv[0]`
451 let mut command = Command::new(executable_path);
451 let mut command = Command::new(executable_path);
452 command.args(args);
452 command.args(args);
453 if let Some(initial) = initial_current_dir {
453 if let Some(initial) = initial_current_dir {
454 command.current_dir(initial);
454 command.current_dir(initial);
455 }
455 }
456 // We don't use subprocess because proper signal handling is harder
456 // We don't use subprocess because proper signal handling is harder
457 // and we don't want to keep `rhg` around after a fallback anyway.
457 // and we don't want to keep `rhg` around after a fallback anyway.
458 // For example, if `rhg` is run in the background and falls back to
458 // For example, if `rhg` is run in the background and falls back to
459 // `hg` which, in turn, waits for a signal, we'll get stuck if
459 // `hg` which, in turn, waits for a signal, we'll get stuck if
460 // we're doing plain subprocess.
460 // we're doing plain subprocess.
461 //
461 //
462 // If `exec` returns, we can only assume our process is very broken
462 // If `exec` returns, we can only assume our process is very broken
463 // (see its documentation), so only try to forward the error code
463 // (see its documentation), so only try to forward the error code
464 // when exiting.
464 // when exiting.
465 let err = command.exec();
465 let err = command.exec();
466 std::process::exit(
466 std::process::exit(
467 err.raw_os_error().unwrap_or(exit_codes::ABORT),
467 err.raw_os_error().unwrap_or(exit_codes::ABORT),
468 );
468 );
469 }
469 }
470 }
470 }
471 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
471 exit_no_fallback(ui, on_unsupported, result, use_detailed_exit_code)
472 }
472 }
473
473
474 fn exit_no_fallback(
474 fn exit_no_fallback(
475 ui: &Ui,
475 ui: &Ui,
476 on_unsupported: OnUnsupported,
476 on_unsupported: OnUnsupported,
477 result: Result<(), CommandError>,
477 result: Result<(), CommandError>,
478 use_detailed_exit_code: bool,
478 use_detailed_exit_code: bool,
479 ) -> ! {
479 ) -> ! {
480 match &result {
480 match &result {
481 Ok(_) => {}
481 Ok(_) => {}
482 Err(CommandError::Unsuccessful) => {}
482 Err(CommandError::Unsuccessful) => {}
483 Err(CommandError::Abort {
483 Err(CommandError::Abort {
484 message,
484 message,
485 detailed_exit_code: _,
485 detailed_exit_code: _,
486 }) => {
486 }) => {
487 if !message.is_empty() {
487 if !message.is_empty() {
488 // Ignore errors when writing to stderr, we’re already exiting
488 // Ignore errors when writing to stderr, we’re already exiting
489 // with failure code so there’s not much more we can do.
489 // with failure code so there’s not much more we can do.
490 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
490 let _ = ui.write_stderr(&format_bytes!(b"{}\n", message));
491 }
491 }
492 }
492 }
493 Err(CommandError::UnsupportedFeature { message }) => {
493 Err(CommandError::UnsupportedFeature { message }) => {
494 match on_unsupported {
494 match on_unsupported {
495 OnUnsupported::Abort => {
495 OnUnsupported::Abort => {
496 let _ = ui.write_stderr(&format_bytes!(
496 let _ = ui.write_stderr(&format_bytes!(
497 b"unsupported feature: {}\n",
497 b"unsupported feature: {}\n",
498 message
498 message
499 ));
499 ));
500 }
500 }
501 OnUnsupported::AbortSilent => {}
501 OnUnsupported::AbortSilent => {}
502 OnUnsupported::Fallback { .. } => unreachable!(),
502 OnUnsupported::Fallback { .. } => unreachable!(),
503 }
503 }
504 }
504 }
505 Err(CommandError::InvalidFallback { path, err }) => {
505 Err(CommandError::InvalidFallback { path, err }) => {
506 let _ = ui.write_stderr(&format_bytes!(
506 let _ = ui.write_stderr(&format_bytes!(
507 b"abort: invalid fallback '{}': {}\n",
507 b"abort: invalid fallback '{}': {}\n",
508 path,
508 path,
509 err.as_bytes(),
509 err.as_bytes(),
510 ));
510 ));
511 }
511 }
512 }
512 }
513 std::process::exit(exit_code(&result, use_detailed_exit_code))
513 std::process::exit(exit_code(&result, use_detailed_exit_code))
514 }
514 }
515
515
516 macro_rules! subcommands {
516 macro_rules! subcommands {
517 ($( $command: ident )+) => {
517 ($( $command: ident )+) => {
518 mod commands {
518 mod commands {
519 $(
519 $(
520 pub mod $command;
520 pub mod $command;
521 )+
521 )+
522 }
522 }
523
523
524 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
524 fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
525 app
525 app
526 $(
526 $(
527 .subcommand(commands::$command::args())
527 .subcommand(commands::$command::args())
528 )+
528 )+
529 }
529 }
530
530
531 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
531 pub type RunFn = fn(&CliInvocation) -> Result<(), CommandError>;
532
532
533 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
533 fn subcommand_run_fn(name: &str) -> Option<RunFn> {
534 match name {
534 match name {
535 $(
535 $(
536 stringify!($command) => Some(commands::$command::run),
536 stringify!($command) => Some(commands::$command::run),
537 )+
537 )+
538 _ => None,
538 _ => None,
539 }
539 }
540 }
540 }
541 };
541 };
542 }
542 }
543
543
544 subcommands! {
544 subcommands! {
545 cat
545 cat
546 debugdata
546 debugdata
547 debugrequirements
547 debugrequirements
548 debugignorerhg
548 debugignorerhg
549 files
549 files
550 root
550 root
551 config
551 config
552 status
552 status
553 }
553 }
554
554
555 pub struct CliInvocation<'a> {
555 pub struct CliInvocation<'a> {
556 ui: &'a Ui,
556 ui: &'a Ui,
557 subcommand_args: &'a ArgMatches<'a>,
557 subcommand_args: &'a ArgMatches<'a>,
558 config: &'a Config,
558 config: &'a Config,
559 /// References inside `Result` is a bit peculiar but allow
559 /// References inside `Result` is a bit peculiar but allow
560 /// `invocation.repo?` to work out with `&CliInvocation` since this
560 /// `invocation.repo?` to work out with `&CliInvocation` since this
561 /// `Result` type is `Copy`.
561 /// `Result` type is `Copy`.
562 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
562 repo: Result<&'a Repo, &'a NoRepoInCwdError>,
563 }
563 }
564
564
565 struct NoRepoInCwdError {
565 struct NoRepoInCwdError {
566 cwd: PathBuf,
566 cwd: PathBuf,
567 }
567 }
568
568
569 /// CLI arguments to be parsed "early" in order to be able to read
569 /// CLI arguments to be parsed "early" in order to be able to read
570 /// configuration before using Clap. Ideally we would also use Clap for this,
570 /// configuration before using Clap. Ideally we would also use Clap for this,
571 /// see <https://github.com/clap-rs/clap/discussions/2366>.
571 /// see <https://github.com/clap-rs/clap/discussions/2366>.
572 ///
572 ///
573 /// These arguments are still declared when we do use Clap later, so that Clap
573 /// These arguments are still declared when we do use Clap later, so that Clap
574 /// does not return an error for their presence.
574 /// does not return an error for their presence.
575 struct EarlyArgs {
575 struct EarlyArgs {
576 /// Values of all `--config` arguments. (Possibly none)
576 /// Values of all `--config` arguments. (Possibly none)
577 config: Vec<Vec<u8>>,
577 config: Vec<Vec<u8>>,
578 /// Value of all the `--color` argument, if any.
578 /// Value of all the `--color` argument, if any.
579 color: Option<Vec<u8>>,
579 color: Option<Vec<u8>>,
580 /// Value of the `-R` or `--repository` argument, if any.
580 /// Value of the `-R` or `--repository` argument, if any.
581 repo: Option<Vec<u8>>,
581 repo: Option<Vec<u8>>,
582 /// Value of the `--cwd` argument, if any.
582 /// Value of the `--cwd` argument, if any.
583 cwd: Option<Vec<u8>>,
583 cwd: Option<Vec<u8>>,
584 }
584 }
585
585
586 impl EarlyArgs {
586 impl EarlyArgs {
587 fn parse<'a>(args: impl IntoIterator<Item = &'a OsString>) -> Self {
587 fn parse<'a>(args: impl IntoIterator<Item = &'a OsString>) -> Self {
588 let mut args = args.into_iter().map(get_bytes_from_os_str);
588 let mut args = args.into_iter().map(get_bytes_from_os_str);
589 let mut config = Vec::new();
589 let mut config = Vec::new();
590 let mut color = None;
590 let mut color = None;
591 let mut repo = None;
591 let mut repo = None;
592 let mut cwd = None;
592 let mut cwd = None;
593 // Use `while let` instead of `for` so that we can also call
593 // Use `while let` instead of `for` so that we can also call
594 // `args.next()` inside the loop.
594 // `args.next()` inside the loop.
595 while let Some(arg) = args.next() {
595 while let Some(arg) = args.next() {
596 if arg == b"--config" {
596 if arg == b"--config" {
597 if let Some(value) = args.next() {
597 if let Some(value) = args.next() {
598 config.push(value)
598 config.push(value)
599 }
599 }
600 } else if let Some(value) = arg.drop_prefix(b"--config=") {
600 } else if let Some(value) = arg.drop_prefix(b"--config=") {
601 config.push(value.to_owned())
601 config.push(value.to_owned())
602 }
602 }
603
603
604 if arg == b"--color" {
604 if arg == b"--color" {
605 if let Some(value) = args.next() {
605 if let Some(value) = args.next() {
606 color = Some(value)
606 color = Some(value)
607 }
607 }
608 } else if let Some(value) = arg.drop_prefix(b"--color=") {
608 } else if let Some(value) = arg.drop_prefix(b"--color=") {
609 color = Some(value.to_owned())
609 color = Some(value.to_owned())
610 }
610 }
611
611
612 if arg == b"--cwd" {
612 if arg == b"--cwd" {
613 if let Some(value) = args.next() {
613 if let Some(value) = args.next() {
614 cwd = Some(value)
614 cwd = Some(value)
615 }
615 }
616 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
616 } else if let Some(value) = arg.drop_prefix(b"--cwd=") {
617 cwd = Some(value.to_owned())
617 cwd = Some(value.to_owned())
618 }
618 }
619
619
620 if arg == b"--repository" || arg == b"-R" {
620 if arg == b"--repository" || arg == b"-R" {
621 if let Some(value) = args.next() {
621 if let Some(value) = args.next() {
622 repo = Some(value)
622 repo = Some(value)
623 }
623 }
624 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
624 } else if let Some(value) = arg.drop_prefix(b"--repository=") {
625 repo = Some(value.to_owned())
625 repo = Some(value.to_owned())
626 } else if let Some(value) = arg.drop_prefix(b"-R") {
626 } else if let Some(value) = arg.drop_prefix(b"-R") {
627 repo = Some(value.to_owned())
627 repo = Some(value.to_owned())
628 }
628 }
629 }
629 }
630 Self {
630 Self {
631 config,
631 config,
632 color,
632 color,
633 repo,
633 repo,
634 cwd,
634 cwd,
635 }
635 }
636 }
636 }
637 }
637 }
638
638
639 /// What to do when encountering some unsupported feature.
639 /// What to do when encountering some unsupported feature.
640 ///
640 ///
641 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
641 /// See `HgError::UnsupportedFeature` and `CommandError::UnsupportedFeature`.
642 enum OnUnsupported {
642 enum OnUnsupported {
643 /// Print an error message describing what feature is not supported,
643 /// Print an error message describing what feature is not supported,
644 /// and exit with code 252.
644 /// and exit with code 252.
645 Abort,
645 Abort,
646 /// Silently exit with code 252.
646 /// Silently exit with code 252.
647 AbortSilent,
647 AbortSilent,
648 /// Try running a Python implementation
648 /// Try running a Python implementation
649 Fallback { executable: Option<Vec<u8>> },
649 Fallback { executable: Option<Vec<u8>> },
650 }
650 }
651
651
652 impl OnUnsupported {
652 impl OnUnsupported {
653 const DEFAULT: Self = OnUnsupported::Abort;
653 const DEFAULT: Self = OnUnsupported::Abort;
654
654
655 fn from_config(config: &Config) -> Self {
655 fn from_config(config: &Config) -> Self {
656 match config
656 match config
657 .get(b"rhg", b"on-unsupported")
657 .get(b"rhg", b"on-unsupported")
658 .map(|value| value.to_ascii_lowercase())
658 .map(|value| value.to_ascii_lowercase())
659 .as_deref()
659 .as_deref()
660 {
660 {
661 Some(b"abort") => OnUnsupported::Abort,
661 Some(b"abort") => OnUnsupported::Abort,
662 Some(b"abort-silent") => OnUnsupported::AbortSilent,
662 Some(b"abort-silent") => OnUnsupported::AbortSilent,
663 Some(b"fallback") => OnUnsupported::Fallback {
663 Some(b"fallback") => OnUnsupported::Fallback {
664 executable: config
664 executable: config
665 .get(b"rhg", b"fallback-executable")
665 .get(b"rhg", b"fallback-executable")
666 .map(|x| x.to_owned()),
666 .map(|x| x.to_owned()),
667 },
667 },
668 None => Self::DEFAULT,
668 None => Self::DEFAULT,
669 Some(_) => {
669 Some(_) => {
670 // TODO: warn about unknown config value
670 // TODO: warn about unknown config value
671 Self::DEFAULT
671 Self::DEFAULT
672 }
672 }
673 }
673 }
674 }
674 }
675 }
675 }
676
676
677 /// The `*` extension is an edge-case for config sub-options that apply to all
677 /// The `*` extension is an edge-case for config sub-options that apply to all
678 /// extensions. For now, only `:required` exists, but that may change in the
678 /// extensions. For now, only `:required` exists, but that may change in the
679 /// future.
679 /// future.
680 const SUPPORTED_EXTENSIONS: &[&[u8]] =
680 const SUPPORTED_EXTENSIONS: &[&[u8]] =
681 &[b"blackbox", b"share", b"sparse", b"narrow", b"*"];
681 &[b"blackbox", b"share", b"sparse", b"narrow", b"*"];
682
682
683 fn check_extensions(config: &Config) -> Result<(), CommandError> {
683 fn check_extensions(config: &Config) -> Result<(), CommandError> {
684 if let Some(b"*") = config.get(b"rhg", b"ignored-extensions") {
684 if let Some(b"*") = config.get(b"rhg", b"ignored-extensions") {
685 // All extensions are to be ignored, nothing to do here
685 // All extensions are to be ignored, nothing to do here
686 return Ok(());
686 return Ok(());
687 }
687 }
688
688
689 let enabled: HashSet<&[u8]> = config
689 let enabled: HashSet<&[u8]> = config
690 .get_section_keys(b"extensions")
690 .get_section_keys(b"extensions")
691 .into_iter()
691 .into_iter()
692 .map(|extension| {
692 .map(|extension| {
693 // Ignore extension suboptions. Only `required` exists for now.
693 // Ignore extension suboptions. Only `required` exists for now.
694 // `rhg` either supports an extension or doesn't, so it doesn't
694 // `rhg` either supports an extension or doesn't, so it doesn't
695 // make sense to consider the loading of an extension.
695 // make sense to consider the loading of an extension.
696 extension.split_2(b':').unwrap_or((extension, b"")).0
696 extension.split_2(b':').unwrap_or((extension, b"")).0
697 })
697 })
698 .collect();
698 .collect();
699
699
700 let mut unsupported = enabled;
700 let mut unsupported = enabled;
701 for supported in SUPPORTED_EXTENSIONS {
701 for supported in SUPPORTED_EXTENSIONS {
702 unsupported.remove(supported);
702 unsupported.remove(supported);
703 }
703 }
704
704
705 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
705 if let Some(ignored_list) = config.get_list(b"rhg", b"ignored-extensions")
706 {
706 {
707 for ignored in ignored_list {
707 for ignored in ignored_list {
708 unsupported.remove(ignored.as_slice());
708 unsupported.remove(ignored.as_slice());
709 }
709 }
710 }
710 }
711
711
712 if unsupported.is_empty() {
712 if unsupported.is_empty() {
713 Ok(())
713 Ok(())
714 } else {
714 } else {
715 let mut unsupported: Vec<_> = unsupported.into_iter().collect();
715 let mut unsupported: Vec<_> = unsupported.into_iter().collect();
716 // Sort the extensions to get a stable output
716 // Sort the extensions to get a stable output
717 unsupported.sort();
717 unsupported.sort();
718 Err(CommandError::UnsupportedFeature {
718 Err(CommandError::UnsupportedFeature {
719 message: format_bytes!(
719 message: format_bytes!(
720 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
720 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
721 join(unsupported, b", ")
721 join(unsupported, b", ")
722 ),
722 ),
723 })
723 })
724 }
724 }
725 }
725 }
726
726
727 /// Array of tuples of (auto upgrade conf, feature conf, local requirement)
727 /// Array of tuples of (auto upgrade conf, feature conf, local requirement)
728 const AUTO_UPGRADES: &[((&str, &str), (&str, &str), &str)] = &[
728 const AUTO_UPGRADES: &[((&str, &str), (&str, &str), &str)] = &[
729 (
729 (
730 ("format", "use-share-safe.automatic-upgrade-of-mismatching-repositories"),
730 ("format", "use-share-safe.automatic-upgrade-of-mismatching-repositories"),
731 ("format", "use-share-safe"),
731 ("format", "use-share-safe"),
732 requirements::SHARESAFE_REQUIREMENT,
732 requirements::SHARESAFE_REQUIREMENT,
733 ),
733 ),
734 (
734 (
735 ("format", "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories"),
735 ("format", "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories"),
736 ("format", "use-dirstate-tracked-hint"),
736 ("format", "use-dirstate-tracked-hint"),
737 requirements::DIRSTATE_TRACKED_HINT_V1,
737 requirements::DIRSTATE_TRACKED_HINT_V1,
738 ),
738 ),
739 (
739 (
740 ("use-dirstate-v2", "automatic-upgrade-of-mismatching-repositories"),
740 ("format", "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories"),
741 ("format", "use-dirstate-v2"),
741 ("format", "use-dirstate-v2"),
742 requirements::DIRSTATE_V2_REQUIREMENT,
742 requirements::DIRSTATE_V2_REQUIREMENT,
743 ),
743 ),
744 ];
744 ];
745
745
746 /// Mercurial allows users to automatically upgrade their repository.
746 /// Mercurial allows users to automatically upgrade their repository.
747 /// `rhg` does not have the ability to upgrade yet, so fallback if an upgrade
747 /// `rhg` does not have the ability to upgrade yet, so fallback if an upgrade
748 /// is needed.
748 /// is needed.
749 fn check_auto_upgrade(
749 fn check_auto_upgrade(
750 config: &Config,
750 config: &Config,
751 reqs: &HashSet<String>,
751 reqs: &HashSet<String>,
752 ) -> Result<(), CommandError> {
752 ) -> Result<(), CommandError> {
753 for (upgrade_conf, feature_conf, local_req) in AUTO_UPGRADES.iter() {
753 for (upgrade_conf, feature_conf, local_req) in AUTO_UPGRADES.iter() {
754 let auto_upgrade = config
754 let auto_upgrade = config
755 .get_bool(upgrade_conf.0.as_bytes(), upgrade_conf.1.as_bytes())?;
755 .get_bool(upgrade_conf.0.as_bytes(), upgrade_conf.1.as_bytes())?;
756
756
757 if auto_upgrade {
757 if auto_upgrade {
758 let want_it = config.get_bool(
758 let want_it = config.get_bool(
759 feature_conf.0.as_bytes(),
759 feature_conf.0.as_bytes(),
760 feature_conf.1.as_bytes(),
760 feature_conf.1.as_bytes(),
761 )?;
761 )?;
762 let have_it = reqs.contains(*local_req);
762 let have_it = reqs.contains(*local_req);
763
763
764 let action = match (want_it, have_it) {
764 let action = match (want_it, have_it) {
765 (true, false) => Some("upgrade"),
765 (true, false) => Some("upgrade"),
766 (false, true) => Some("downgrade"),
766 (false, true) => Some("downgrade"),
767 _ => None,
767 _ => None,
768 };
768 };
769 if let Some(action) = action {
769 if let Some(action) = action {
770 let message = format!(
770 let message = format!(
771 "automatic {} {}.{}",
771 "automatic {} {}.{}",
772 action, upgrade_conf.0, upgrade_conf.1
772 action, upgrade_conf.0, upgrade_conf.1
773 );
773 );
774 return Err(CommandError::unsupported(message));
774 return Err(CommandError::unsupported(message));
775 }
775 }
776 }
776 }
777 }
777 }
778 Ok(())
778 Ok(())
779 }
779 }
780
780
781 fn check_unsupported(
781 fn check_unsupported(
782 config: &Config,
782 config: &Config,
783 repo: Result<&Repo, &NoRepoInCwdError>,
783 repo: Result<&Repo, &NoRepoInCwdError>,
784 ) -> Result<(), CommandError> {
784 ) -> Result<(), CommandError> {
785 check_extensions(config)?;
785 check_extensions(config)?;
786
786
787 if std::env::var_os("HG_PENDING").is_some() {
787 if std::env::var_os("HG_PENDING").is_some() {
788 // TODO: only if the value is `== repo.working_directory`?
788 // TODO: only if the value is `== repo.working_directory`?
789 // What about relative v.s. absolute paths?
789 // What about relative v.s. absolute paths?
790 Err(CommandError::unsupported("$HG_PENDING"))?
790 Err(CommandError::unsupported("$HG_PENDING"))?
791 }
791 }
792
792
793 if let Ok(repo) = repo {
793 if let Ok(repo) = repo {
794 if repo.has_subrepos()? {
794 if repo.has_subrepos()? {
795 Err(CommandError::unsupported("sub-repositories"))?
795 Err(CommandError::unsupported("sub-repositories"))?
796 }
796 }
797 check_auto_upgrade(config, repo.requirements())?;
797 check_auto_upgrade(config, repo.requirements())?;
798 }
798 }
799
799
800 if config.has_non_empty_section(b"encode") {
800 if config.has_non_empty_section(b"encode") {
801 Err(CommandError::unsupported("[encode] config"))?
801 Err(CommandError::unsupported("[encode] config"))?
802 }
802 }
803
803
804 if config.has_non_empty_section(b"decode") {
804 if config.has_non_empty_section(b"decode") {
805 Err(CommandError::unsupported("[decode] config"))?
805 Err(CommandError::unsupported("[decode] config"))?
806 }
806 }
807
807
808 Ok(())
808 Ok(())
809 }
809 }
@@ -1,238 +1,243 b''
1 ===============================
1 ===============================
2 Test the "tracked hint" feature
2 Test the "tracked hint" feature
3 ===============================
3 ===============================
4
4
5 The tracked hint feature provide a file that get updated when the set of tracked
5 The tracked hint feature provide a file that get updated when the set of tracked
6 files get updated.
6 files get updated.
7
7
8 basic setup
8 basic setup
9
9
10 $ cat << EOF >> $HGRCPATH
10 $ cat << EOF >> $HGRCPATH
11 > [format]
11 > [format]
12 > use-dirstate-tracked-hint=yes
12 > use-dirstate-tracked-hint=yes
13 > EOF
13 > EOF
14
14
15 $ hg init tracked-hint-test
15 $ hg init tracked-hint-test
16 $ cd tracked-hint-test
16 $ cd tracked-hint-test
17 $ hg debugbuilddag '.+10' -n
17 $ hg debugbuilddag '.+10' -n
18 $ hg log -G -T '{rev} {desc} {files}\n'
18 $ hg log -G -T '{rev} {desc} {files}\n'
19 o 10 r10 nf10
19 o 10 r10 nf10
20 |
20 |
21 o 9 r9 nf9
21 o 9 r9 nf9
22 |
22 |
23 o 8 r8 nf8
23 o 8 r8 nf8
24 |
24 |
25 o 7 r7 nf7
25 o 7 r7 nf7
26 |
26 |
27 o 6 r6 nf6
27 o 6 r6 nf6
28 |
28 |
29 o 5 r5 nf5
29 o 5 r5 nf5
30 |
30 |
31 o 4 r4 nf4
31 o 4 r4 nf4
32 |
32 |
33 o 3 r3 nf3
33 o 3 r3 nf3
34 |
34 |
35 o 2 r2 nf2
35 o 2 r2 nf2
36 |
36 |
37 o 1 r1 nf1
37 o 1 r1 nf1
38 |
38 |
39 o 0 r0 nf0
39 o 0 r0 nf0
40
40
41 $ hg up tip
41 $ hg up tip
42 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
42 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 $ hg files
43 $ hg files
44 nf0
44 nf0
45 nf1
45 nf1
46 nf10
46 nf10
47 nf2
47 nf2
48 nf3
48 nf3
49 nf4
49 nf4
50 nf5
50 nf5
51 nf6
51 nf6
52 nf7
52 nf7
53 nf8
53 nf8
54 nf9
54 nf9
55
55
56 key-file exists
56 key-file exists
57 -----------
57 -----------
58
58
59 The tracked hint file should exist
59 The tracked hint file should exist
60
60
61 $ ls -1 .hg/dirstate*
61 $ ls -1 .hg/dirstate*
62 .hg/dirstate
62 .hg/dirstate
63 .hg/dirstate-tracked-hint
63 .hg/dirstate-tracked-hint
64
64
65 key-file stay the same if the tracked set is unchanged
65 key-file stay the same if the tracked set is unchanged
66 ------------------------------------------------------
66 ------------------------------------------------------
67
67
68 (copy its content for later comparison)
68 (copy its content for later comparison)
69
69
70 $ cp .hg/dirstate-tracked-hint ../key-bck
70 $ cp .hg/dirstate-tracked-hint ../key-bck
71 $ echo foo >> nf0
71 $ echo foo >> nf0
72 $ sleep 1
72 $ sleep 1
73 $ hg status
73 $ hg status
74 M nf0
74 M nf0
75 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
75 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
76 $ hg revert -C nf0
76 $ hg revert -C nf0
77 $ sleep 1
77 $ sleep 1
78 $ hg status
78 $ hg status
79 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
79 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
80
80
81 key-file change if the tracked set is changed manually
81 key-file change if the tracked set is changed manually
82 ------------------------------------------------------
82 ------------------------------------------------------
83
83
84 adding a file to tracking
84 adding a file to tracking
85
85
86 $ cp .hg/dirstate-tracked-hint ../key-bck
86 $ cp .hg/dirstate-tracked-hint ../key-bck
87 $ echo x > x
87 $ echo x > x
88 $ hg add x
88 $ hg add x
89 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
89 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
90 Files .hg/dirstate-tracked-hint and ../key-bck differ
90 Files .hg/dirstate-tracked-hint and ../key-bck differ
91 [1]
91 [1]
92
92
93 remove a file from tracking
93 remove a file from tracking
94 (forget)
94 (forget)
95
95
96 $ cp .hg/dirstate-tracked-hint ../key-bck
96 $ cp .hg/dirstate-tracked-hint ../key-bck
97 $ hg forget x
97 $ hg forget x
98 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
98 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
99 Files .hg/dirstate-tracked-hint and ../key-bck differ
99 Files .hg/dirstate-tracked-hint and ../key-bck differ
100 [1]
100 [1]
101
101
102 (remove)
102 (remove)
103
103
104 $ cp .hg/dirstate-tracked-hint ../key-bck
104 $ cp .hg/dirstate-tracked-hint ../key-bck
105 $ hg remove nf1
105 $ hg remove nf1
106 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
106 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
107 Files .hg/dirstate-tracked-hint and ../key-bck differ
107 Files .hg/dirstate-tracked-hint and ../key-bck differ
108 [1]
108 [1]
109
109
110 key-file changes on revert (when applicable)
110 key-file changes on revert (when applicable)
111 --------------------------------------------
111 --------------------------------------------
112
112
113 $ cp .hg/dirstate-tracked-hint ../key-bck
113 $ cp .hg/dirstate-tracked-hint ../key-bck
114 $ hg status
114 $ hg status
115 R nf1
115 R nf1
116 ? x
116 ? x
117 $ hg revert --all
117 $ hg revert --all
118 undeleting nf1
118 undeleting nf1
119 $ hg status
119 $ hg status
120 ? x
120 ? x
121 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
121 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
122 Files .hg/dirstate-tracked-hint and ../key-bck differ
122 Files .hg/dirstate-tracked-hint and ../key-bck differ
123 [1]
123 [1]
124
124
125
125
126 `hg update` does affect the key-file (when needed)
126 `hg update` does affect the key-file (when needed)
127 --------------------------------------------------
127 --------------------------------------------------
128
128
129 update changing the tracked set
129 update changing the tracked set
130
130
131 (removing)
131 (removing)
132
132
133 $ cp .hg/dirstate-tracked-hint ../key-bck
133 $ cp .hg/dirstate-tracked-hint ../key-bck
134 $ hg status --rev . --rev '.#generations[-1]'
134 $ hg status --rev . --rev '.#generations[-1]'
135 R nf10
135 R nf10
136 $ hg up '.#generations[-1]'
136 $ hg up '.#generations[-1]'
137 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
137 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
138 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
138 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
139 Files .hg/dirstate-tracked-hint and ../key-bck differ
139 Files .hg/dirstate-tracked-hint and ../key-bck differ
140 [1]
140 [1]
141
141
142 (adding)
142 (adding)
143
143
144 $ cp .hg/dirstate-tracked-hint ../key-bck
144 $ cp .hg/dirstate-tracked-hint ../key-bck
145 $ hg status --rev . --rev '.#generations[1]'
145 $ hg status --rev . --rev '.#generations[1]'
146 A nf10
146 A nf10
147 $ hg up '.#generations[1]'
147 $ hg up '.#generations[1]'
148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
149 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
150 Files .hg/dirstate-tracked-hint and ../key-bck differ
150 Files .hg/dirstate-tracked-hint and ../key-bck differ
151 [1]
151 [1]
152
152
153 update not affecting the tracked set
153 update not affecting the tracked set
154
154
155 $ echo foo >> nf0
155 $ echo foo >> nf0
156 $ hg commit -m foo
156 $ hg commit -m foo
157
157
158 $ cp .hg/dirstate-tracked-hint ../key-bck
158 $ cp .hg/dirstate-tracked-hint ../key-bck
159 $ hg status --rev . --rev '.#generations[-1]'
159 $ hg status --rev . --rev '.#generations[-1]'
160 M nf0
160 M nf0
161 $ hg up '.#generations[-1]'
161 $ hg up '.#generations[-1]'
162 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
162 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
163 $ diff --brief .hg/dirstate-tracked-hint ../key-bck
164
164
165 Test upgrade and downgrade
165 Test upgrade and downgrade
166 ==========================
166 ==========================
167
167
168 $ ls .hg/dirstate-tracked-hint
168 $ ls .hg/dirstate-tracked-hint
169 .hg/dirstate-tracked-hint
169 .hg/dirstate-tracked-hint
170 $ hg debugrequires | grep 'tracked'
170 $ hg debugrequires | grep 'tracked'
171 dirstate-tracked-key-v1
171 dirstate-tracked-key-v1
172
172
173 downgrade
173 downgrade
174
174
175 $ hg debugupgraderepo --config format.use-dirstate-tracked-hint=no --run --quiet
175 $ hg debugupgraderepo --config format.use-dirstate-tracked-hint=no --run --quiet
176 upgrade will perform the following actions:
176 upgrade will perform the following actions:
177
177
178 requirements
178 requirements
179 preserved: * (glob)
179 preserved: * (glob)
180 removed: dirstate-tracked-key-v1
180 removed: dirstate-tracked-key-v1
181
181
182 no revlogs to process
182 no revlogs to process
183
183
184 $ ls -1 .hg/dirstate-tracked-hint
184 $ ls -1 .hg/dirstate-tracked-hint
185 ls: *.hg/dirstate-tracked-hint*: $ENOENT$ (glob)
185 ls: *.hg/dirstate-tracked-hint*: $ENOENT$ (glob)
186 [2]
186 [2]
187 $ hg debugrequires | grep 'tracked'
187 $ hg debugrequires | grep 'tracked'
188 [1]
188 [1]
189
189
190 upgrade
190 upgrade
191
191
192 $ hg debugupgraderepo --config format.use-dirstate-tracked-hint=yes --run --quiet
192 $ hg debugupgraderepo --config format.use-dirstate-tracked-hint=yes --run --quiet
193 upgrade will perform the following actions:
193 upgrade will perform the following actions:
194
194
195 requirements
195 requirements
196 preserved: * (glob)
196 preserved: * (glob)
197 added: dirstate-tracked-key-v1
197 added: dirstate-tracked-key-v1
198
198
199 no revlogs to process
199 no revlogs to process
200
200
201 $ ls -1 .hg/dirstate-tracked-hint
201 $ ls -1 .hg/dirstate-tracked-hint
202 .hg/dirstate-tracked-hint
202 .hg/dirstate-tracked-hint
203 $ hg debugrequires | grep 'tracked'
203 $ hg debugrequires | grep 'tracked'
204 dirstate-tracked-key-v1
204 dirstate-tracked-key-v1
205 $ cd ..
205 $ cd ..
206
206
207 Test automatic upgrade and downgrade
207 Test automatic upgrade and downgrade
208 ------------------------------------
208 ------------------------------------
209
209
210 create an initial repository
210 create an initial repository
211
211
212 $ hg init auto-upgrade \
212 $ hg init auto-upgrade \
213 > --config format.use-dirstate-tracked-hint=no
213 > --config format.use-dirstate-tracked-hint=no
214 $ hg debugbuilddag -R auto-upgrade --new-file .+5
214 $ hg debugbuilddag -R auto-upgrade --new-file .+5
215 $ hg -R auto-upgrade update
215 $ hg -R auto-upgrade update
216 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
216 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
217 $ hg debugformat -R auto-upgrade | grep tracked
217 $ hg debugformat -R auto-upgrade | grep tracked
218 tracked-hint: no
218 tracked-hint: no
219
219
220 upgrade it to dirstate-tracked-hint automatically
220 upgrade it to dirstate-tracked-hint automatically
221
221
222 $ hg status -R auto-upgrade \
222 $ hg status -R auto-upgrade \
223 > --config format.use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories=yes \
223 > --config format.use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories=yes \
224 > --config format.use-dirstate-tracked-hint=yes
224 > --config format.use-dirstate-tracked-hint=yes
225 automatically upgrading repository to the `tracked-hint` feature
225 automatically upgrading repository to the `tracked-hint` feature
226 (see `hg help config.format.use-dirstate-tracked-hint` for details)
226 (see `hg help config.format.use-dirstate-tracked-hint` for details)
227 $ hg debugformat -R auto-upgrade | grep tracked
227 $ hg debugformat -R auto-upgrade | grep tracked
228 tracked-hint: yes
228 tracked-hint: yes
229
229
230 rhg supports this feature
231
232 $ hg status -R auto-upgrade \
233 > --config format.use-dirstate-tracked-hint=yes --config rhg.on-unsupported=abort
234
230 downgrade it from dirstate-tracked-hint automatically
235 downgrade it from dirstate-tracked-hint automatically
231
236
232 $ hg status -R auto-upgrade \
237 $ hg status -R auto-upgrade \
233 > --config format.use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories=yes \
238 > --config format.use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories=yes \
234 > --config format.use-dirstate-tracked-hint=no
239 > --config format.use-dirstate-tracked-hint=no
235 automatically downgrading repository from the `tracked-hint` feature
240 automatically downgrading repository from the `tracked-hint` feature
236 (see `hg help config.format.use-dirstate-tracked-hint` for details)
241 (see `hg help config.format.use-dirstate-tracked-hint` for details)
237 $ hg debugformat -R auto-upgrade | grep tracked
242 $ hg debugformat -R auto-upgrade | grep tracked
238 tracked-hint: no
243 tracked-hint: no
General Comments 0
You need to be logged in to leave comments. Login now