##// END OF EJS Templates
dirstate: deal with read-race for pure rust code path (rhg)...
marmoute -
r51134:491f3dd0 stable
parent child Browse files
Show More
@@ -1,214 +1,220 b''
1 use crate::config::ConfigValueParseError;
1 use crate::config::ConfigValueParseError;
2 use crate::exit_codes;
2 use crate::exit_codes;
3 use std::fmt;
3 use std::fmt;
4
4
5 /// Common error cases that can happen in many different APIs
5 /// Common error cases that can happen in many different APIs
6 #[derive(Debug, derive_more::From)]
6 #[derive(Debug, derive_more::From)]
7 pub enum HgError {
7 pub enum HgError {
8 IoError {
8 IoError {
9 error: std::io::Error,
9 error: std::io::Error,
10 context: IoErrorContext,
10 context: IoErrorContext,
11 },
11 },
12
12
13 /// A file under `.hg/` normally only written by Mercurial is not in the
13 /// A file under `.hg/` normally only written by Mercurial is not in the
14 /// expected format. This indicates a bug in Mercurial, filesystem
14 /// expected format. This indicates a bug in Mercurial, filesystem
15 /// corruption, or hardware failure.
15 /// corruption, or hardware failure.
16 ///
16 ///
17 /// The given string is a short explanation for users, not intended to be
17 /// The given string is a short explanation for users, not intended to be
18 /// machine-readable.
18 /// machine-readable.
19 CorruptedRepository(String),
19 CorruptedRepository(String),
20
20
21 /// The respository or requested operation involves a feature not
21 /// The respository or requested operation involves a feature not
22 /// supported by the Rust implementation. Falling back to the Python
22 /// supported by the Rust implementation. Falling back to the Python
23 /// implementation may or may not work.
23 /// implementation may or may not work.
24 ///
24 ///
25 /// The given string is a short explanation for users, not intended to be
25 /// The given string is a short explanation for users, not intended to be
26 /// machine-readable.
26 /// machine-readable.
27 UnsupportedFeature(String),
27 UnsupportedFeature(String),
28
28
29 /// Operation cannot proceed for some other reason.
29 /// Operation cannot proceed for some other reason.
30 ///
30 ///
31 /// The message is a short explanation for users, not intended to be
31 /// The message is a short explanation for users, not intended to be
32 /// machine-readable.
32 /// machine-readable.
33 Abort {
33 Abort {
34 message: String,
34 message: String,
35 detailed_exit_code: exit_codes::ExitCode,
35 detailed_exit_code: exit_codes::ExitCode,
36 hint: Option<String>,
36 hint: Option<String>,
37 },
37 },
38
38
39 /// A configuration value is not in the expected syntax.
39 /// A configuration value is not in the expected syntax.
40 ///
40 ///
41 /// These errors can happen in many places in the code because values are
41 /// These errors can happen in many places in the code because values are
42 /// parsed lazily as the file-level parser does not know the expected type
42 /// parsed lazily as the file-level parser does not know the expected type
43 /// and syntax of each value.
43 /// and syntax of each value.
44 #[from]
44 #[from]
45 ConfigValueParseError(ConfigValueParseError),
45 ConfigValueParseError(ConfigValueParseError),
46
46
47 /// Censored revision data.
47 /// Censored revision data.
48 CensoredNodeError,
48 CensoredNodeError,
49 /// A race condition has been detected. This *must* be handled locally
50 /// and not directly surface to the user.
51 RaceDetected(String),
49 }
52 }
50
53
51 /// Details about where an I/O error happened
54 /// Details about where an I/O error happened
52 #[derive(Debug)]
55 #[derive(Debug)]
53 pub enum IoErrorContext {
56 pub enum IoErrorContext {
54 /// `std::fs::metadata`
57 /// `std::fs::metadata`
55 ReadingMetadata(std::path::PathBuf),
58 ReadingMetadata(std::path::PathBuf),
56 ReadingFile(std::path::PathBuf),
59 ReadingFile(std::path::PathBuf),
57 WritingFile(std::path::PathBuf),
60 WritingFile(std::path::PathBuf),
58 RemovingFile(std::path::PathBuf),
61 RemovingFile(std::path::PathBuf),
59 RenamingFile {
62 RenamingFile {
60 from: std::path::PathBuf,
63 from: std::path::PathBuf,
61 to: std::path::PathBuf,
64 to: std::path::PathBuf,
62 },
65 },
63 /// `std::fs::canonicalize`
66 /// `std::fs::canonicalize`
64 CanonicalizingPath(std::path::PathBuf),
67 CanonicalizingPath(std::path::PathBuf),
65 /// `std::env::current_dir`
68 /// `std::env::current_dir`
66 CurrentDir,
69 CurrentDir,
67 /// `std::env::current_exe`
70 /// `std::env::current_exe`
68 CurrentExe,
71 CurrentExe,
69 }
72 }
70
73
71 impl HgError {
74 impl HgError {
72 pub fn corrupted(explanation: impl Into<String>) -> Self {
75 pub fn corrupted(explanation: impl Into<String>) -> Self {
73 // TODO: capture a backtrace here and keep it in the error value
76 // TODO: capture a backtrace here and keep it in the error value
74 // to aid debugging?
77 // to aid debugging?
75 // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html
78 // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html
76 HgError::CorruptedRepository(explanation.into())
79 HgError::CorruptedRepository(explanation.into())
77 }
80 }
78
81
79 pub fn unsupported(explanation: impl Into<String>) -> Self {
82 pub fn unsupported(explanation: impl Into<String>) -> Self {
80 HgError::UnsupportedFeature(explanation.into())
83 HgError::UnsupportedFeature(explanation.into())
81 }
84 }
82
85
83 pub fn abort(
86 pub fn abort(
84 explanation: impl Into<String>,
87 explanation: impl Into<String>,
85 exit_code: exit_codes::ExitCode,
88 exit_code: exit_codes::ExitCode,
86 hint: Option<String>,
89 hint: Option<String>,
87 ) -> Self {
90 ) -> Self {
88 HgError::Abort {
91 HgError::Abort {
89 message: explanation.into(),
92 message: explanation.into(),
90 detailed_exit_code: exit_code,
93 detailed_exit_code: exit_code,
91 hint,
94 hint,
92 }
95 }
93 }
96 }
94 }
97 }
95
98
96 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
99 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
97 impl fmt::Display for HgError {
100 impl fmt::Display for HgError {
98 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99 match self {
102 match self {
100 HgError::Abort { message, .. } => write!(f, "{}", message),
103 HgError::Abort { message, .. } => write!(f, "{}", message),
101 HgError::IoError { error, context } => {
104 HgError::IoError { error, context } => {
102 write!(f, "abort: {}: {}", context, error)
105 write!(f, "abort: {}: {}", context, error)
103 }
106 }
104 HgError::CorruptedRepository(explanation) => {
107 HgError::CorruptedRepository(explanation) => {
105 write!(f, "abort: {}", explanation)
108 write!(f, "abort: {}", explanation)
106 }
109 }
107 HgError::UnsupportedFeature(explanation) => {
110 HgError::UnsupportedFeature(explanation) => {
108 write!(f, "unsupported feature: {}", explanation)
111 write!(f, "unsupported feature: {}", explanation)
109 }
112 }
110 HgError::CensoredNodeError => {
113 HgError::CensoredNodeError => {
111 write!(f, "encountered a censored node")
114 write!(f, "encountered a censored node")
112 }
115 }
113 HgError::ConfigValueParseError(error) => error.fmt(f),
116 HgError::ConfigValueParseError(error) => error.fmt(f),
117 HgError::RaceDetected(context) => {
118 write!(f, "encountered a race condition {context}")
119 }
114 }
120 }
115 }
121 }
116 }
122 }
117
123
118 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
124 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
119 impl fmt::Display for IoErrorContext {
125 impl fmt::Display for IoErrorContext {
120 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
121 match self {
127 match self {
122 IoErrorContext::ReadingMetadata(path) => {
128 IoErrorContext::ReadingMetadata(path) => {
123 write!(f, "when reading metadata of {}", path.display())
129 write!(f, "when reading metadata of {}", path.display())
124 }
130 }
125 IoErrorContext::ReadingFile(path) => {
131 IoErrorContext::ReadingFile(path) => {
126 write!(f, "when reading {}", path.display())
132 write!(f, "when reading {}", path.display())
127 }
133 }
128 IoErrorContext::WritingFile(path) => {
134 IoErrorContext::WritingFile(path) => {
129 write!(f, "when writing {}", path.display())
135 write!(f, "when writing {}", path.display())
130 }
136 }
131 IoErrorContext::RemovingFile(path) => {
137 IoErrorContext::RemovingFile(path) => {
132 write!(f, "when removing {}", path.display())
138 write!(f, "when removing {}", path.display())
133 }
139 }
134 IoErrorContext::RenamingFile { from, to } => write!(
140 IoErrorContext::RenamingFile { from, to } => write!(
135 f,
141 f,
136 "when renaming {} to {}",
142 "when renaming {} to {}",
137 from.display(),
143 from.display(),
138 to.display()
144 to.display()
139 ),
145 ),
140 IoErrorContext::CanonicalizingPath(path) => {
146 IoErrorContext::CanonicalizingPath(path) => {
141 write!(f, "when canonicalizing {}", path.display())
147 write!(f, "when canonicalizing {}", path.display())
142 }
148 }
143 IoErrorContext::CurrentDir => {
149 IoErrorContext::CurrentDir => {
144 write!(f, "error getting current working directory")
150 write!(f, "error getting current working directory")
145 }
151 }
146 IoErrorContext::CurrentExe => {
152 IoErrorContext::CurrentExe => {
147 write!(f, "error getting current executable")
153 write!(f, "error getting current executable")
148 }
154 }
149 }
155 }
150 }
156 }
151 }
157 }
152
158
153 pub trait IoResultExt<T> {
159 pub trait IoResultExt<T> {
154 /// Annotate a possible I/O error as related to a reading a file at the
160 /// Annotate a possible I/O error as related to a reading a file at the
155 /// given path.
161 /// given path.
156 ///
162 ///
157 /// This allows printing something like β€œFile not found when reading
163 /// This allows printing something like β€œFile not found when reading
158 /// example.txt” instead of just β€œFile not found”.
164 /// example.txt” instead of just β€œFile not found”.
159 ///
165 ///
160 /// Converts a `Result` with `std::io::Error` into one with `HgError`.
166 /// Converts a `Result` with `std::io::Error` into one with `HgError`.
161 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError>;
167 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError>;
162
168
163 fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError>;
169 fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError>;
164
170
165 fn with_context(
171 fn with_context(
166 self,
172 self,
167 context: impl FnOnce() -> IoErrorContext,
173 context: impl FnOnce() -> IoErrorContext,
168 ) -> Result<T, HgError>;
174 ) -> Result<T, HgError>;
169 }
175 }
170
176
171 impl<T> IoResultExt<T> for std::io::Result<T> {
177 impl<T> IoResultExt<T> for std::io::Result<T> {
172 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError> {
178 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError> {
173 self.with_context(|| IoErrorContext::ReadingFile(path.to_owned()))
179 self.with_context(|| IoErrorContext::ReadingFile(path.to_owned()))
174 }
180 }
175
181
176 fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError> {
182 fn when_writing_file(self, path: &std::path::Path) -> Result<T, HgError> {
177 self.with_context(|| IoErrorContext::WritingFile(path.to_owned()))
183 self.with_context(|| IoErrorContext::WritingFile(path.to_owned()))
178 }
184 }
179
185
180 fn with_context(
186 fn with_context(
181 self,
187 self,
182 context: impl FnOnce() -> IoErrorContext,
188 context: impl FnOnce() -> IoErrorContext,
183 ) -> Result<T, HgError> {
189 ) -> Result<T, HgError> {
184 self.map_err(|error| HgError::IoError {
190 self.map_err(|error| HgError::IoError {
185 error,
191 error,
186 context: context(),
192 context: context(),
187 })
193 })
188 }
194 }
189 }
195 }
190
196
191 pub trait HgResultExt<T> {
197 pub trait HgResultExt<T> {
192 /// Handle missing files separately from other I/O error cases.
198 /// Handle missing files separately from other I/O error cases.
193 ///
199 ///
194 /// Wraps the `Ok` type in an `Option`:
200 /// Wraps the `Ok` type in an `Option`:
195 ///
201 ///
196 /// * `Ok(x)` becomes `Ok(Some(x))`
202 /// * `Ok(x)` becomes `Ok(Some(x))`
197 /// * An I/O "not found" error becomes `Ok(None)`
203 /// * An I/O "not found" error becomes `Ok(None)`
198 /// * Other errors are unchanged
204 /// * Other errors are unchanged
199 fn io_not_found_as_none(self) -> Result<Option<T>, HgError>;
205 fn io_not_found_as_none(self) -> Result<Option<T>, HgError>;
200 }
206 }
201
207
202 impl<T> HgResultExt<T> for Result<T, HgError> {
208 impl<T> HgResultExt<T> for Result<T, HgError> {
203 fn io_not_found_as_none(self) -> Result<Option<T>, HgError> {
209 fn io_not_found_as_none(self) -> Result<Option<T>, HgError> {
204 match self {
210 match self {
205 Ok(x) => Ok(Some(x)),
211 Ok(x) => Ok(Some(x)),
206 Err(HgError::IoError { error, .. })
212 Err(HgError::IoError { error, .. })
207 if error.kind() == std::io::ErrorKind::NotFound =>
213 if error.kind() == std::io::ErrorKind::NotFound =>
208 {
214 {
209 Ok(None)
215 Ok(None)
210 }
216 }
211 Err(other_error) => Err(other_error),
217 Err(other_error) => Err(other_error),
212 }
218 }
213 }
219 }
214 }
220 }
@@ -1,619 +1,688 b''
1 use crate::changelog::Changelog;
1 use crate::changelog::Changelog;
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
4 use crate::dirstate_tree::dirstate_map::DirstateMapWriteMode;
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
6 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::dirstate_tree::owning::OwningDirstateMap;
7 use crate::errors::HgResultExt;
7 use crate::errors::HgResultExt;
8 use crate::errors::{HgError, IoResultExt};
8 use crate::errors::{HgError, IoResultExt};
9 use crate::lock::{try_with_lock_no_wait, LockError};
9 use crate::lock::{try_with_lock_no_wait, LockError};
10 use crate::manifest::{Manifest, Manifestlog};
10 use crate::manifest::{Manifest, Manifestlog};
11 use crate::revlog::filelog::Filelog;
11 use crate::revlog::filelog::Filelog;
12 use crate::revlog::revlog::RevlogError;
12 use crate::revlog::revlog::RevlogError;
13 use crate::utils::debug::debug_wait_for_file_or_print;
13 use crate::utils::debug::debug_wait_for_file_or_print;
14 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::files::get_path_from_bytes;
15 use crate::utils::hg_path::HgPath;
15 use crate::utils::hg_path::HgPath;
16 use crate::utils::SliceExt;
16 use crate::utils::SliceExt;
17 use crate::vfs::{is_dir, is_file, Vfs};
17 use crate::vfs::{is_dir, is_file, Vfs};
18 use crate::{requirements, NodePrefix};
18 use crate::{requirements, NodePrefix};
19 use crate::{DirstateError, Revision};
19 use crate::{DirstateError, Revision};
20 use std::cell::{Ref, RefCell, RefMut};
20 use std::cell::{Ref, RefCell, RefMut};
21 use std::collections::HashSet;
21 use std::collections::HashSet;
22 use std::io::Seek;
22 use std::io::Seek;
23 use std::io::SeekFrom;
23 use std::io::SeekFrom;
24 use std::io::Write as IoWrite;
24 use std::io::Write as IoWrite;
25 use std::path::{Path, PathBuf};
25 use std::path::{Path, PathBuf};
26
26
27 const V2_MAX_READ_ATTEMPTS: usize = 5;
28
27 /// A repository on disk
29 /// A repository on disk
28 pub struct Repo {
30 pub struct Repo {
29 working_directory: PathBuf,
31 working_directory: PathBuf,
30 dot_hg: PathBuf,
32 dot_hg: PathBuf,
31 store: PathBuf,
33 store: PathBuf,
32 requirements: HashSet<String>,
34 requirements: HashSet<String>,
33 config: Config,
35 config: Config,
34 dirstate_parents: LazyCell<DirstateParents>,
36 dirstate_parents: LazyCell<DirstateParents>,
35 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>>,
37 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>>,
36 dirstate_map: LazyCell<OwningDirstateMap>,
38 dirstate_map: LazyCell<OwningDirstateMap>,
37 changelog: LazyCell<Changelog>,
39 changelog: LazyCell<Changelog>,
38 manifestlog: LazyCell<Manifestlog>,
40 manifestlog: LazyCell<Manifestlog>,
39 }
41 }
40
42
41 #[derive(Debug, derive_more::From)]
43 #[derive(Debug, derive_more::From)]
42 pub enum RepoError {
44 pub enum RepoError {
43 NotFound {
45 NotFound {
44 at: PathBuf,
46 at: PathBuf,
45 },
47 },
46 #[from]
48 #[from]
47 ConfigParseError(ConfigParseError),
49 ConfigParseError(ConfigParseError),
48 #[from]
50 #[from]
49 Other(HgError),
51 Other(HgError),
50 }
52 }
51
53
52 impl From<ConfigError> for RepoError {
54 impl From<ConfigError> for RepoError {
53 fn from(error: ConfigError) -> Self {
55 fn from(error: ConfigError) -> Self {
54 match error {
56 match error {
55 ConfigError::Parse(error) => error.into(),
57 ConfigError::Parse(error) => error.into(),
56 ConfigError::Other(error) => error.into(),
58 ConfigError::Other(error) => error.into(),
57 }
59 }
58 }
60 }
59 }
61 }
60
62
61 impl Repo {
63 impl Repo {
62 /// tries to find nearest repository root in current working directory or
64 /// tries to find nearest repository root in current working directory or
63 /// its ancestors
65 /// its ancestors
64 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
66 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
65 let current_directory = crate::utils::current_dir()?;
67 let current_directory = crate::utils::current_dir()?;
66 // ancestors() is inclusive: it first yields `current_directory`
68 // ancestors() is inclusive: it first yields `current_directory`
67 // as-is.
69 // as-is.
68 for ancestor in current_directory.ancestors() {
70 for ancestor in current_directory.ancestors() {
69 if is_dir(ancestor.join(".hg"))? {
71 if is_dir(ancestor.join(".hg"))? {
70 return Ok(ancestor.to_path_buf());
72 return Ok(ancestor.to_path_buf());
71 }
73 }
72 }
74 }
73 return Err(RepoError::NotFound {
75 return Err(RepoError::NotFound {
74 at: current_directory,
76 at: current_directory,
75 });
77 });
76 }
78 }
77
79
78 /// Find a repository, either at the given path (which must contain a `.hg`
80 /// Find a repository, either at the given path (which must contain a `.hg`
79 /// sub-directory) or by searching the current directory and its
81 /// sub-directory) or by searching the current directory and its
80 /// ancestors.
82 /// ancestors.
81 ///
83 ///
82 /// A method with two very different "modes" like this usually a code smell
84 /// A method with two very different "modes" like this usually a code smell
83 /// to make two methods instead, but in this case an `Option` is what rhg
85 /// to make two methods instead, but in this case an `Option` is what rhg
84 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
86 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
85 /// Having two methods would just move that `if` to almost all callers.
87 /// Having two methods would just move that `if` to almost all callers.
86 pub fn find(
88 pub fn find(
87 config: &Config,
89 config: &Config,
88 explicit_path: Option<PathBuf>,
90 explicit_path: Option<PathBuf>,
89 ) -> Result<Self, RepoError> {
91 ) -> Result<Self, RepoError> {
90 if let Some(root) = explicit_path {
92 if let Some(root) = explicit_path {
91 if is_dir(root.join(".hg"))? {
93 if is_dir(root.join(".hg"))? {
92 Self::new_at_path(root.to_owned(), config)
94 Self::new_at_path(root.to_owned(), config)
93 } else if is_file(&root)? {
95 } else if is_file(&root)? {
94 Err(HgError::unsupported("bundle repository").into())
96 Err(HgError::unsupported("bundle repository").into())
95 } else {
97 } else {
96 Err(RepoError::NotFound {
98 Err(RepoError::NotFound {
97 at: root.to_owned(),
99 at: root.to_owned(),
98 })
100 })
99 }
101 }
100 } else {
102 } else {
101 let root = Self::find_repo_root()?;
103 let root = Self::find_repo_root()?;
102 Self::new_at_path(root, config)
104 Self::new_at_path(root, config)
103 }
105 }
104 }
106 }
105
107
106 /// To be called after checking that `.hg` is a sub-directory
108 /// To be called after checking that `.hg` is a sub-directory
107 fn new_at_path(
109 fn new_at_path(
108 working_directory: PathBuf,
110 working_directory: PathBuf,
109 config: &Config,
111 config: &Config,
110 ) -> Result<Self, RepoError> {
112 ) -> Result<Self, RepoError> {
111 let dot_hg = working_directory.join(".hg");
113 let dot_hg = working_directory.join(".hg");
112
114
113 let mut repo_config_files = Vec::new();
115 let mut repo_config_files = Vec::new();
114 repo_config_files.push(dot_hg.join("hgrc"));
116 repo_config_files.push(dot_hg.join("hgrc"));
115 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
117 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
116
118
117 let hg_vfs = Vfs { base: &dot_hg };
119 let hg_vfs = Vfs { base: &dot_hg };
118 let mut reqs = requirements::load_if_exists(hg_vfs)?;
120 let mut reqs = requirements::load_if_exists(hg_vfs)?;
119 let relative =
121 let relative =
120 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
122 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
121 let shared =
123 let shared =
122 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
124 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
123
125
124 // From `mercurial/localrepo.py`:
126 // From `mercurial/localrepo.py`:
125 //
127 //
126 // if .hg/requires contains the sharesafe requirement, it means
128 // if .hg/requires contains the sharesafe requirement, it means
127 // there exists a `.hg/store/requires` too and we should read it
129 // there exists a `.hg/store/requires` too and we should read it
128 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
130 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
129 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
131 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
130 // is not present, refer checkrequirementscompat() for that
132 // is not present, refer checkrequirementscompat() for that
131 //
133 //
132 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
134 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
133 // repository was shared the old way. We check the share source
135 // repository was shared the old way. We check the share source
134 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
136 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
135 // current repository needs to be reshared
137 // current repository needs to be reshared
136 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
138 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
137
139
138 let store_path;
140 let store_path;
139 if !shared {
141 if !shared {
140 store_path = dot_hg.join("store");
142 store_path = dot_hg.join("store");
141 } else {
143 } else {
142 let bytes = hg_vfs.read("sharedpath")?;
144 let bytes = hg_vfs.read("sharedpath")?;
143 let mut shared_path =
145 let mut shared_path =
144 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
146 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
145 .to_owned();
147 .to_owned();
146 if relative {
148 if relative {
147 shared_path = dot_hg.join(shared_path)
149 shared_path = dot_hg.join(shared_path)
148 }
150 }
149 if !is_dir(&shared_path)? {
151 if !is_dir(&shared_path)? {
150 return Err(HgError::corrupted(format!(
152 return Err(HgError::corrupted(format!(
151 ".hg/sharedpath points to nonexistent directory {}",
153 ".hg/sharedpath points to nonexistent directory {}",
152 shared_path.display()
154 shared_path.display()
153 ))
155 ))
154 .into());
156 .into());
155 }
157 }
156
158
157 store_path = shared_path.join("store");
159 store_path = shared_path.join("store");
158
160
159 let source_is_share_safe =
161 let source_is_share_safe =
160 requirements::load(Vfs { base: &shared_path })?
162 requirements::load(Vfs { base: &shared_path })?
161 .contains(requirements::SHARESAFE_REQUIREMENT);
163 .contains(requirements::SHARESAFE_REQUIREMENT);
162
164
163 if share_safe != source_is_share_safe {
165 if share_safe != source_is_share_safe {
164 return Err(HgError::unsupported("share-safe mismatch").into());
166 return Err(HgError::unsupported("share-safe mismatch").into());
165 }
167 }
166
168
167 if share_safe {
169 if share_safe {
168 repo_config_files.insert(0, shared_path.join("hgrc"))
170 repo_config_files.insert(0, shared_path.join("hgrc"))
169 }
171 }
170 }
172 }
171 if share_safe {
173 if share_safe {
172 reqs.extend(requirements::load(Vfs { base: &store_path })?);
174 reqs.extend(requirements::load(Vfs { base: &store_path })?);
173 }
175 }
174
176
175 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
177 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
176 config.combine_with_repo(&repo_config_files)?
178 config.combine_with_repo(&repo_config_files)?
177 } else {
179 } else {
178 config.clone()
180 config.clone()
179 };
181 };
180
182
181 let repo = Self {
183 let repo = Self {
182 requirements: reqs,
184 requirements: reqs,
183 working_directory,
185 working_directory,
184 store: store_path,
186 store: store_path,
185 dot_hg,
187 dot_hg,
186 config: repo_config,
188 config: repo_config,
187 dirstate_parents: LazyCell::new(),
189 dirstate_parents: LazyCell::new(),
188 dirstate_data_file_uuid: LazyCell::new(),
190 dirstate_data_file_uuid: LazyCell::new(),
189 dirstate_map: LazyCell::new(),
191 dirstate_map: LazyCell::new(),
190 changelog: LazyCell::new(),
192 changelog: LazyCell::new(),
191 manifestlog: LazyCell::new(),
193 manifestlog: LazyCell::new(),
192 };
194 };
193
195
194 requirements::check(&repo)?;
196 requirements::check(&repo)?;
195
197
196 Ok(repo)
198 Ok(repo)
197 }
199 }
198
200
199 pub fn working_directory_path(&self) -> &Path {
201 pub fn working_directory_path(&self) -> &Path {
200 &self.working_directory
202 &self.working_directory
201 }
203 }
202
204
203 pub fn requirements(&self) -> &HashSet<String> {
205 pub fn requirements(&self) -> &HashSet<String> {
204 &self.requirements
206 &self.requirements
205 }
207 }
206
208
207 pub fn config(&self) -> &Config {
209 pub fn config(&self) -> &Config {
208 &self.config
210 &self.config
209 }
211 }
210
212
211 /// For accessing repository files (in `.hg`), except for the store
213 /// For accessing repository files (in `.hg`), except for the store
212 /// (`.hg/store`).
214 /// (`.hg/store`).
213 pub fn hg_vfs(&self) -> Vfs<'_> {
215 pub fn hg_vfs(&self) -> Vfs<'_> {
214 Vfs { base: &self.dot_hg }
216 Vfs { base: &self.dot_hg }
215 }
217 }
216
218
217 /// For accessing repository store files (in `.hg/store`)
219 /// For accessing repository store files (in `.hg/store`)
218 pub fn store_vfs(&self) -> Vfs<'_> {
220 pub fn store_vfs(&self) -> Vfs<'_> {
219 Vfs { base: &self.store }
221 Vfs { base: &self.store }
220 }
222 }
221
223
222 /// For accessing the working copy
224 /// For accessing the working copy
223 pub fn working_directory_vfs(&self) -> Vfs<'_> {
225 pub fn working_directory_vfs(&self) -> Vfs<'_> {
224 Vfs {
226 Vfs {
225 base: &self.working_directory,
227 base: &self.working_directory,
226 }
228 }
227 }
229 }
228
230
229 pub fn try_with_wlock_no_wait<R>(
231 pub fn try_with_wlock_no_wait<R>(
230 &self,
232 &self,
231 f: impl FnOnce() -> R,
233 f: impl FnOnce() -> R,
232 ) -> Result<R, LockError> {
234 ) -> Result<R, LockError> {
233 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
235 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
234 }
236 }
235
237
236 pub fn has_dirstate_v2(&self) -> bool {
238 pub fn has_dirstate_v2(&self) -> bool {
237 self.requirements
239 self.requirements
238 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
240 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
239 }
241 }
240
242
241 pub fn has_sparse(&self) -> bool {
243 pub fn has_sparse(&self) -> bool {
242 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
244 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
243 }
245 }
244
246
245 pub fn has_narrow(&self) -> bool {
247 pub fn has_narrow(&self) -> bool {
246 self.requirements.contains(requirements::NARROW_REQUIREMENT)
248 self.requirements.contains(requirements::NARROW_REQUIREMENT)
247 }
249 }
248
250
249 pub fn has_nodemap(&self) -> bool {
251 pub fn has_nodemap(&self) -> bool {
250 self.requirements
252 self.requirements
251 .contains(requirements::NODEMAP_REQUIREMENT)
253 .contains(requirements::NODEMAP_REQUIREMENT)
252 }
254 }
253
255
254 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
256 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
255 Ok(self
257 Ok(self
256 .hg_vfs()
258 .hg_vfs()
257 .read("dirstate")
259 .read("dirstate")
258 .io_not_found_as_none()?
260 .io_not_found_as_none()?
259 .unwrap_or(Vec::new()))
261 .unwrap_or(Vec::new()))
260 }
262 }
261
263
262 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
264 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
263 Ok(*self
265 Ok(*self
264 .dirstate_parents
266 .dirstate_parents
265 .get_or_init(|| self.read_dirstate_parents())?)
267 .get_or_init(|| self.read_dirstate_parents())?)
266 }
268 }
267
269
268 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
270 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
269 let dirstate = self.dirstate_file_contents()?;
271 let dirstate = self.dirstate_file_contents()?;
270 let parents = if dirstate.is_empty() {
272 let parents = if dirstate.is_empty() {
271 if self.has_dirstate_v2() {
273 if self.has_dirstate_v2() {
272 self.dirstate_data_file_uuid.set(None);
274 self.dirstate_data_file_uuid.set(None);
273 }
275 }
274 DirstateParents::NULL
276 DirstateParents::NULL
275 } else if self.has_dirstate_v2() {
277 } else if self.has_dirstate_v2() {
276 let docket =
278 let docket =
277 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
279 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
278 self.dirstate_data_file_uuid
280 self.dirstate_data_file_uuid
279 .set(Some(docket.uuid.to_owned()));
281 .set(Some(docket.uuid.to_owned()));
280 docket.parents()
282 docket.parents()
281 } else {
283 } else {
282 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
284 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
283 .clone()
285 .clone()
284 };
286 };
285 self.dirstate_parents.set(parents);
287 self.dirstate_parents.set(parents);
286 Ok(parents)
288 Ok(parents)
287 }
289 }
288
290
289 fn read_dirstate_data_file_uuid(
291 fn read_dirstate_data_file_uuid(
290 &self,
292 &self,
291 ) -> Result<Option<Vec<u8>>, HgError> {
293 ) -> Result<Option<Vec<u8>>, HgError> {
292 assert!(
294 assert!(
293 self.has_dirstate_v2(),
295 self.has_dirstate_v2(),
294 "accessing dirstate data file ID without dirstate-v2"
296 "accessing dirstate data file ID without dirstate-v2"
295 );
297 );
296 let dirstate = self.dirstate_file_contents()?;
298 let dirstate = self.dirstate_file_contents()?;
297 if dirstate.is_empty() {
299 if dirstate.is_empty() {
298 self.dirstate_parents.set(DirstateParents::NULL);
300 self.dirstate_parents.set(DirstateParents::NULL);
299 Ok(None)
301 Ok(None)
300 } else {
302 } else {
301 let docket =
303 let docket =
302 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
304 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
303 self.dirstate_parents.set(docket.parents());
305 self.dirstate_parents.set(docket.parents());
304 Ok(Some(docket.uuid.to_owned()))
306 Ok(Some(docket.uuid.to_owned()))
305 }
307 }
306 }
308 }
307
309
308 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
310 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
309 if self.has_dirstate_v2() {
311 if self.has_dirstate_v2() {
310 self.read_docket_and_data_file()
312 // The v2 dirstate is split into a docket and a data file.
313 // Since we don't always take the `wlock` to read it
314 // (like in `hg status`), it is susceptible to races.
315 // A simple retry method should be enough since full rewrites
316 // only happen when too much garbage data is present and
317 // this race is unlikely.
318 let mut tries = 0;
319
320 while tries < V2_MAX_READ_ATTEMPTS {
321 tries += 1;
322 match self.read_docket_and_data_file() {
323 Ok(m) => {
324 return Ok(m);
325 }
326 Err(e) => match e {
327 DirstateError::Common(HgError::RaceDetected(
328 context,
329 )) => {
330 log::info!(
331 "dirstate read race detected {} (retry {}/{})",
332 context,
333 tries,
334 V2_MAX_READ_ATTEMPTS,
335 );
336 continue;
337 }
338 _ => return Err(e.into()),
339 },
340 }
341 }
342 let error = HgError::abort(
343 format!("dirstate read race happened {tries} times in a row"),
344 255,
345 None,
346 );
347 return Err(DirstateError::Common(error));
311 } else {
348 } else {
312 debug_wait_for_file_or_print(
349 debug_wait_for_file_or_print(
313 self.config(),
350 self.config(),
314 "dirstate.pre-read-file",
351 "dirstate.pre-read-file",
315 );
352 );
316 let dirstate_file_contents = self.dirstate_file_contents()?;
353 let dirstate_file_contents = self.dirstate_file_contents()?;
317 if dirstate_file_contents.is_empty() {
354 return if dirstate_file_contents.is_empty() {
318 self.dirstate_parents.set(DirstateParents::NULL);
355 self.dirstate_parents.set(DirstateParents::NULL);
319 Ok(OwningDirstateMap::new_empty(Vec::new()))
356 Ok(OwningDirstateMap::new_empty(Vec::new()))
320 } else {
357 } else {
321 let (map, parents) =
358 let (map, parents) =
322 OwningDirstateMap::new_v1(dirstate_file_contents)?;
359 OwningDirstateMap::new_v1(dirstate_file_contents)?;
323 self.dirstate_parents.set(parents);
360 self.dirstate_parents.set(parents);
324 Ok(map)
361 Ok(map)
325 }
362 };
326 }
363 }
327 }
364 }
328
365
329 fn read_docket_and_data_file(
366 fn read_docket_and_data_file(
330 &self,
367 &self,
331 ) -> Result<OwningDirstateMap, DirstateError> {
368 ) -> Result<OwningDirstateMap, DirstateError> {
332 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
369 debug_wait_for_file_or_print(self.config(), "dirstate.pre-read-file");
333 let dirstate_file_contents = self.dirstate_file_contents()?;
370 let dirstate_file_contents = self.dirstate_file_contents()?;
334 if dirstate_file_contents.is_empty() {
371 if dirstate_file_contents.is_empty() {
335 self.dirstate_parents.set(DirstateParents::NULL);
372 self.dirstate_parents.set(DirstateParents::NULL);
336 self.dirstate_data_file_uuid.set(None);
373 self.dirstate_data_file_uuid.set(None);
337 return Ok(OwningDirstateMap::new_empty(Vec::new()));
374 return Ok(OwningDirstateMap::new_empty(Vec::new()));
338 }
375 }
339 let docket = crate::dirstate_tree::on_disk::read_docket(
376 let docket = crate::dirstate_tree::on_disk::read_docket(
340 &dirstate_file_contents,
377 &dirstate_file_contents,
341 )?;
378 )?;
342 debug_wait_for_file_or_print(
379 debug_wait_for_file_or_print(
343 self.config(),
380 self.config(),
344 "dirstate.post-docket-read-file",
381 "dirstate.post-docket-read-file",
345 );
382 );
346 self.dirstate_parents.set(docket.parents());
383 self.dirstate_parents.set(docket.parents());
347 self.dirstate_data_file_uuid
384 self.dirstate_data_file_uuid
348 .set(Some(docket.uuid.to_owned()));
385 .set(Some(docket.uuid.to_owned()));
349 let data_size = docket.data_size();
386 let data_size = docket.data_size();
387
388 let context = "between reading dirstate docket and data file";
389 let race_error = HgError::RaceDetected(context.into());
350 let metadata = docket.tree_metadata();
390 let metadata = docket.tree_metadata();
391
351 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
392 let mut map = if crate::vfs::is_on_nfs_mount(docket.data_filename()) {
352 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
393 // Don't mmap on NFS to prevent `SIGBUS` error on deletion
353 OwningDirstateMap::new_v2(
394 let contents = self.hg_vfs().read(docket.data_filename());
354 self.hg_vfs().read(docket.data_filename())?,
395 let contents = match contents {
355 data_size,
396 Ok(c) => c,
356 metadata,
397 Err(HgError::IoError { error, context }) => {
398 match error.raw_os_error().expect("real os error") {
399 // 2 = ENOENT, No such file or directory
400 // 116 = ESTALE, Stale NFS file handle
401 //
402 // TODO match on `error.kind()` when
403 // `ErrorKind::StaleNetworkFileHandle` is stable.
404 2 | 116 => {
405 // Race where the data file was deleted right after
406 // we read the docket, try again
407 return Err(race_error.into());
408 }
409 _ => {
410 return Err(
411 HgError::IoError { error, context }.into()
357 )
412 )
358 } else if let Some(data_mmap) = self
413 }
414 }
415 }
416 Err(e) => return Err(e.into()),
417 };
418 OwningDirstateMap::new_v2(contents, data_size, metadata)
419 } else {
420 match self
359 .hg_vfs()
421 .hg_vfs()
360 .mmap_open(docket.data_filename())
422 .mmap_open(docket.data_filename())
361 .io_not_found_as_none()?
423 .io_not_found_as_none()
362 {
424 {
425 Ok(Some(data_mmap)) => {
363 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
426 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
364 } else {
427 }
365 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
428 Ok(None) => {
429 // Race where the data file was deleted right after we
430 // read the docket, try again
431 return Err(race_error.into());
432 }
433 Err(e) => return Err(e.into()),
434 }
366 }?;
435 }?;
367
436
368 let write_mode_config = self
437 let write_mode_config = self
369 .config()
438 .config()
370 .get_str(b"devel", b"dirstate.v2.data_update_mode")
439 .get_str(b"devel", b"dirstate.v2.data_update_mode")
371 .unwrap_or(Some("auto"))
440 .unwrap_or(Some("auto"))
372 .unwrap_or("auto"); // don't bother for devel options
441 .unwrap_or("auto"); // don't bother for devel options
373 let write_mode = match write_mode_config {
442 let write_mode = match write_mode_config {
374 "auto" => DirstateMapWriteMode::Auto,
443 "auto" => DirstateMapWriteMode::Auto,
375 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
444 "force-new" => DirstateMapWriteMode::ForceNewDataFile,
376 "force-append" => DirstateMapWriteMode::ForceAppend,
445 "force-append" => DirstateMapWriteMode::ForceAppend,
377 _ => DirstateMapWriteMode::Auto,
446 _ => DirstateMapWriteMode::Auto,
378 };
447 };
379
448
380 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
449 map.with_dmap_mut(|m| m.set_write_mode(write_mode));
381
450
382 Ok(map)
451 Ok(map)
383 }
452 }
384
453
385 pub fn dirstate_map(
454 pub fn dirstate_map(
386 &self,
455 &self,
387 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
456 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
388 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
457 self.dirstate_map.get_or_init(|| self.new_dirstate_map())
389 }
458 }
390
459
391 pub fn dirstate_map_mut(
460 pub fn dirstate_map_mut(
392 &self,
461 &self,
393 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
462 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
394 self.dirstate_map
463 self.dirstate_map
395 .get_mut_or_init(|| self.new_dirstate_map())
464 .get_mut_or_init(|| self.new_dirstate_map())
396 }
465 }
397
466
398 fn new_changelog(&self) -> Result<Changelog, HgError> {
467 fn new_changelog(&self) -> Result<Changelog, HgError> {
399 Changelog::open(&self.store_vfs(), self.has_nodemap())
468 Changelog::open(&self.store_vfs(), self.has_nodemap())
400 }
469 }
401
470
402 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
471 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
403 self.changelog.get_or_init(|| self.new_changelog())
472 self.changelog.get_or_init(|| self.new_changelog())
404 }
473 }
405
474
406 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
475 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
407 self.changelog.get_mut_or_init(|| self.new_changelog())
476 self.changelog.get_mut_or_init(|| self.new_changelog())
408 }
477 }
409
478
410 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
479 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
411 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
480 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
412 }
481 }
413
482
414 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
483 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
415 self.manifestlog.get_or_init(|| self.new_manifestlog())
484 self.manifestlog.get_or_init(|| self.new_manifestlog())
416 }
485 }
417
486
418 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
487 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
419 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
488 self.manifestlog.get_mut_or_init(|| self.new_manifestlog())
420 }
489 }
421
490
422 /// Returns the manifest of the *changeset* with the given node ID
491 /// Returns the manifest of the *changeset* with the given node ID
423 pub fn manifest_for_node(
492 pub fn manifest_for_node(
424 &self,
493 &self,
425 node: impl Into<NodePrefix>,
494 node: impl Into<NodePrefix>,
426 ) -> Result<Manifest, RevlogError> {
495 ) -> Result<Manifest, RevlogError> {
427 self.manifestlog()?.data_for_node(
496 self.manifestlog()?.data_for_node(
428 self.changelog()?
497 self.changelog()?
429 .data_for_node(node.into())?
498 .data_for_node(node.into())?
430 .manifest_node()?
499 .manifest_node()?
431 .into(),
500 .into(),
432 )
501 )
433 }
502 }
434
503
435 /// Returns the manifest of the *changeset* with the given revision number
504 /// Returns the manifest of the *changeset* with the given revision number
436 pub fn manifest_for_rev(
505 pub fn manifest_for_rev(
437 &self,
506 &self,
438 revision: Revision,
507 revision: Revision,
439 ) -> Result<Manifest, RevlogError> {
508 ) -> Result<Manifest, RevlogError> {
440 self.manifestlog()?.data_for_node(
509 self.manifestlog()?.data_for_node(
441 self.changelog()?
510 self.changelog()?
442 .data_for_rev(revision)?
511 .data_for_rev(revision)?
443 .manifest_node()?
512 .manifest_node()?
444 .into(),
513 .into(),
445 )
514 )
446 }
515 }
447
516
448 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
517 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
449 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
518 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
450 Ok(entry.tracked())
519 Ok(entry.tracked())
451 } else {
520 } else {
452 Ok(false)
521 Ok(false)
453 }
522 }
454 }
523 }
455
524
456 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
525 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
457 Filelog::open(self, path)
526 Filelog::open(self, path)
458 }
527 }
459
528
460 /// Write to disk any updates that were made through `dirstate_map_mut`.
529 /// Write to disk any updates that were made through `dirstate_map_mut`.
461 ///
530 ///
462 /// The "wlock" must be held while calling this.
531 /// The "wlock" must be held while calling this.
463 /// See for example `try_with_wlock_no_wait`.
532 /// See for example `try_with_wlock_no_wait`.
464 ///
533 ///
465 /// TODO: have a `WritableRepo` type only accessible while holding the
534 /// TODO: have a `WritableRepo` type only accessible while holding the
466 /// lock?
535 /// lock?
467 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
536 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
468 let map = self.dirstate_map()?;
537 let map = self.dirstate_map()?;
469 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
538 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
470 // it’s unset
539 // it’s unset
471 let parents = self.dirstate_parents()?;
540 let parents = self.dirstate_parents()?;
472 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
541 let (packed_dirstate, old_uuid_to_remove) = if self.has_dirstate_v2() {
473 let uuid_opt = self
542 let uuid_opt = self
474 .dirstate_data_file_uuid
543 .dirstate_data_file_uuid
475 .get_or_init(|| self.read_dirstate_data_file_uuid())?;
544 .get_or_init(|| self.read_dirstate_data_file_uuid())?;
476 let uuid_opt = uuid_opt.as_ref();
545 let uuid_opt = uuid_opt.as_ref();
477 let write_mode = if uuid_opt.is_some() {
546 let write_mode = if uuid_opt.is_some() {
478 DirstateMapWriteMode::Auto
547 DirstateMapWriteMode::Auto
479 } else {
548 } else {
480 DirstateMapWriteMode::ForceNewDataFile
549 DirstateMapWriteMode::ForceNewDataFile
481 };
550 };
482 let (data, tree_metadata, append, old_data_size) =
551 let (data, tree_metadata, append, old_data_size) =
483 map.pack_v2(write_mode)?;
552 map.pack_v2(write_mode)?;
484
553
485 // Reuse the uuid, or generate a new one, keeping the old for
554 // Reuse the uuid, or generate a new one, keeping the old for
486 // deletion.
555 // deletion.
487 let (uuid, old_uuid) = match uuid_opt {
556 let (uuid, old_uuid) = match uuid_opt {
488 Some(uuid) => {
557 Some(uuid) => {
489 let as_str = std::str::from_utf8(uuid)
558 let as_str = std::str::from_utf8(uuid)
490 .map_err(|_| {
559 .map_err(|_| {
491 HgError::corrupted(
560 HgError::corrupted(
492 "non-UTF-8 dirstate data file ID",
561 "non-UTF-8 dirstate data file ID",
493 )
562 )
494 })?
563 })?
495 .to_owned();
564 .to_owned();
496 if append {
565 if append {
497 (as_str, None)
566 (as_str, None)
498 } else {
567 } else {
499 (DirstateDocket::new_uid(), Some(as_str))
568 (DirstateDocket::new_uid(), Some(as_str))
500 }
569 }
501 }
570 }
502 None => (DirstateDocket::new_uid(), None),
571 None => (DirstateDocket::new_uid(), None),
503 };
572 };
504
573
505 let data_filename = format!("dirstate.{}", uuid);
574 let data_filename = format!("dirstate.{}", uuid);
506 let data_filename = self.hg_vfs().join(data_filename);
575 let data_filename = self.hg_vfs().join(data_filename);
507 let mut options = std::fs::OpenOptions::new();
576 let mut options = std::fs::OpenOptions::new();
508 options.write(true);
577 options.write(true);
509
578
510 // Why are we not using the O_APPEND flag when appending?
579 // Why are we not using the O_APPEND flag when appending?
511 //
580 //
512 // - O_APPEND makes it trickier to deal with garbage at the end of
581 // - O_APPEND makes it trickier to deal with garbage at the end of
513 // the file, left by a previous uncommitted transaction. By
582 // the file, left by a previous uncommitted transaction. By
514 // starting the write at [old_data_size] we make sure we erase
583 // starting the write at [old_data_size] we make sure we erase
515 // all such garbage.
584 // all such garbage.
516 //
585 //
517 // - O_APPEND requires to special-case 0-byte writes, whereas we
586 // - O_APPEND requires to special-case 0-byte writes, whereas we
518 // don't need that.
587 // don't need that.
519 //
588 //
520 // - Some OSes have bugs in implementation O_APPEND:
589 // - Some OSes have bugs in implementation O_APPEND:
521 // revlog.py talks about a Solaris bug, but we also saw some ZFS
590 // revlog.py talks about a Solaris bug, but we also saw some ZFS
522 // bug: https://github.com/openzfs/zfs/pull/3124,
591 // bug: https://github.com/openzfs/zfs/pull/3124,
523 // https://github.com/openzfs/zfs/issues/13370
592 // https://github.com/openzfs/zfs/issues/13370
524 //
593 //
525 if !append {
594 if !append {
526 log::trace!("creating a new dirstate data file");
595 log::trace!("creating a new dirstate data file");
527 options.create_new(true);
596 options.create_new(true);
528 } else {
597 } else {
529 log::trace!("appending to the dirstate data file");
598 log::trace!("appending to the dirstate data file");
530 }
599 }
531
600
532 let data_size = (|| {
601 let data_size = (|| {
533 // TODO: loop and try another random ID if !append and this
602 // TODO: loop and try another random ID if !append and this
534 // returns `ErrorKind::AlreadyExists`? Collision chance of two
603 // returns `ErrorKind::AlreadyExists`? Collision chance of two
535 // random IDs is one in 2**32
604 // random IDs is one in 2**32
536 let mut file = options.open(&data_filename)?;
605 let mut file = options.open(&data_filename)?;
537 if append {
606 if append {
538 file.seek(SeekFrom::Start(old_data_size as u64))?;
607 file.seek(SeekFrom::Start(old_data_size as u64))?;
539 }
608 }
540 file.write_all(&data)?;
609 file.write_all(&data)?;
541 file.flush()?;
610 file.flush()?;
542 file.seek(SeekFrom::Current(0))
611 file.seek(SeekFrom::Current(0))
543 })()
612 })()
544 .when_writing_file(&data_filename)?;
613 .when_writing_file(&data_filename)?;
545
614
546 let packed_dirstate = DirstateDocket::serialize(
615 let packed_dirstate = DirstateDocket::serialize(
547 parents,
616 parents,
548 tree_metadata,
617 tree_metadata,
549 data_size,
618 data_size,
550 uuid.as_bytes(),
619 uuid.as_bytes(),
551 )
620 )
552 .map_err(|_: std::num::TryFromIntError| {
621 .map_err(|_: std::num::TryFromIntError| {
553 HgError::corrupted("overflow in dirstate docket serialization")
622 HgError::corrupted("overflow in dirstate docket serialization")
554 })?;
623 })?;
555
624
556 (packed_dirstate, old_uuid)
625 (packed_dirstate, old_uuid)
557 } else {
626 } else {
558 (map.pack_v1(parents)?, None)
627 (map.pack_v1(parents)?, None)
559 };
628 };
560
629
561 let vfs = self.hg_vfs();
630 let vfs = self.hg_vfs();
562 vfs.atomic_write("dirstate", &packed_dirstate)?;
631 vfs.atomic_write("dirstate", &packed_dirstate)?;
563 if let Some(uuid) = old_uuid_to_remove {
632 if let Some(uuid) = old_uuid_to_remove {
564 // Remove the old data file after the new docket pointing to the
633 // Remove the old data file after the new docket pointing to the
565 // new data file was written.
634 // new data file was written.
566 vfs.remove_file(format!("dirstate.{}", uuid))?;
635 vfs.remove_file(format!("dirstate.{}", uuid))?;
567 }
636 }
568 Ok(())
637 Ok(())
569 }
638 }
570 }
639 }
571
640
572 /// Lazily-initialized component of `Repo` with interior mutability
641 /// Lazily-initialized component of `Repo` with interior mutability
573 ///
642 ///
574 /// This differs from `OnceCell` in that the value can still be "deinitialized"
643 /// This differs from `OnceCell` in that the value can still be "deinitialized"
575 /// later by setting its inner `Option` to `None`. It also takes the
644 /// later by setting its inner `Option` to `None`. It also takes the
576 /// initialization function as an argument when the value is requested, not
645 /// initialization function as an argument when the value is requested, not
577 /// when the instance is created.
646 /// when the instance is created.
578 struct LazyCell<T> {
647 struct LazyCell<T> {
579 value: RefCell<Option<T>>,
648 value: RefCell<Option<T>>,
580 }
649 }
581
650
582 impl<T> LazyCell<T> {
651 impl<T> LazyCell<T> {
583 fn new() -> Self {
652 fn new() -> Self {
584 Self {
653 Self {
585 value: RefCell::new(None),
654 value: RefCell::new(None),
586 }
655 }
587 }
656 }
588
657
589 fn set(&self, value: T) {
658 fn set(&self, value: T) {
590 *self.value.borrow_mut() = Some(value)
659 *self.value.borrow_mut() = Some(value)
591 }
660 }
592
661
593 fn get_or_init<E>(
662 fn get_or_init<E>(
594 &self,
663 &self,
595 init: impl Fn() -> Result<T, E>,
664 init: impl Fn() -> Result<T, E>,
596 ) -> Result<Ref<T>, E> {
665 ) -> Result<Ref<T>, E> {
597 let mut borrowed = self.value.borrow();
666 let mut borrowed = self.value.borrow();
598 if borrowed.is_none() {
667 if borrowed.is_none() {
599 drop(borrowed);
668 drop(borrowed);
600 // Only use `borrow_mut` if it is really needed to avoid panic in
669 // Only use `borrow_mut` if it is really needed to avoid panic in
601 // case there is another outstanding borrow but mutation is not
670 // case there is another outstanding borrow but mutation is not
602 // needed.
671 // needed.
603 *self.value.borrow_mut() = Some(init()?);
672 *self.value.borrow_mut() = Some(init()?);
604 borrowed = self.value.borrow()
673 borrowed = self.value.borrow()
605 }
674 }
606 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
675 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
607 }
676 }
608
677
609 fn get_mut_or_init<E>(
678 fn get_mut_or_init<E>(
610 &self,
679 &self,
611 init: impl Fn() -> Result<T, E>,
680 init: impl Fn() -> Result<T, E>,
612 ) -> Result<RefMut<T>, E> {
681 ) -> Result<RefMut<T>, E> {
613 let mut borrowed = self.value.borrow_mut();
682 let mut borrowed = self.value.borrow_mut();
614 if borrowed.is_none() {
683 if borrowed.is_none() {
615 *borrowed = Some(init()?);
684 *borrowed = Some(init()?);
616 }
685 }
617 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
686 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
618 }
687 }
619 }
688 }
@@ -1,584 +1,597 b''
1 ==============================================================================
1 ==============================================================================
2 Check potential race conditions between a dirstate's read and other operations
2 Check potential race conditions between a dirstate's read and other operations
3 ==============================================================================
3 ==============================================================================
4
4
5 #testcases dirstate-v1 dirstate-v2-append dirstate-v2-rewrite
5 #testcases dirstate-v1 dirstate-v2-append dirstate-v2-rewrite
6 #testcases pre-all-read pre-some-read
6 #testcases pre-all-read pre-some-read
7
7
8 Some commands, like `hg status`, do not need to take the wlock but need to
8 Some commands, like `hg status`, do not need to take the wlock but need to
9 access dirstate data.
9 access dirstate data.
10 Other commands might update the dirstate data while this happens.
10 Other commands might update the dirstate data while this happens.
11
11
12 This can create issues if repository data is read in the wrong order, or for
12 This can create issues if repository data is read in the wrong order, or for
13 the dirstate-v2 format where the data is contained in multiple files.
13 the dirstate-v2 format where the data is contained in multiple files.
14
14
15 This test file is meant to test various cases where such parallel operations
15 This test file is meant to test various cases where such parallel operations
16 happen and make sure the reading process behaves fine. We do so with a `hg
16 happen and make sure the reading process behaves fine. We do so with a `hg
17 status` command since it is probably the most advanced of such read-only
17 status` command since it is probably the most advanced of such read-only
18 command.
18 command.
19
19
20 It bears simililarity with `tests/test-dirstate-status-race.t ` but tests a
20 It bears simililarity with `tests/test-dirstate-status-race.t ` but tests a
21 different type of race.
21 different type of race.
22
22
23 Setup
23 Setup
24 =====
24 =====
25
25
26 $ cat >> $HGRCPATH << EOF
26 $ cat >> $HGRCPATH << EOF
27 > [storage]
27 > [storage]
28 > dirstate-v2.slow-path=allow
28 > dirstate-v2.slow-path=allow
29 > EOF
29 > EOF
30
30
31 #if no-dirstate-v1
31 #if no-dirstate-v1
32 $ cat >> $HGRCPATH << EOF
32 $ cat >> $HGRCPATH << EOF
33 > [format]
33 > [format]
34 > use-dirstate-v2=yes
34 > use-dirstate-v2=yes
35 > EOF
35 > EOF
36 #else
36 #else
37 $ cat >> $HGRCPATH << EOF
37 $ cat >> $HGRCPATH << EOF
38 > [format]
38 > [format]
39 > use-dirstate-v2=no
39 > use-dirstate-v2=no
40 > EOF
40 > EOF
41 #endif
41 #endif
42
42
43 #if dirstate-v2-rewrite
43 #if dirstate-v2-rewrite
44 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
44 $ d2args="--config devel.dirstate.v2.data_update_mode=force-new"
45 #endif
45 #endif
46 #if dirstate-v2-append
46 #if dirstate-v2-append
47 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
47 $ d2args="--config devel.dirstate.v2.data_update_mode=force-append"
48 #endif
48 #endif
49
49
50
50
51 #if dirstate-v1
51 #if dirstate-v1
52 $ cfg="devel.sync.dirstate.pre-read-file"
52 $ cfg="devel.sync.dirstate.pre-read-file"
53 #else
53 #else
54 #if pre-all-read
54 #if pre-all-read
55 $ cfg="devel.sync.dirstate.pre-read-file"
55 $ cfg="devel.sync.dirstate.pre-read-file"
56 #else
56 #else
57 $ cfg="devel.sync.dirstate.post-docket-read-file"
57 $ cfg="devel.sync.dirstate.post-docket-read-file"
58 #endif
58 #endif
59 #endif
59 #endif
60
60
61 $ directories="dir dir/nested dir2"
61 $ directories="dir dir/nested dir2"
62 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
62 $ first_files="dir/nested/a dir/b dir/c dir/d dir2/e f"
63 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
63 $ second_files="g dir/nested/h dir/i dir/j dir2/k dir2/l dir/nested/m"
64 $ extra_files="dir/n dir/o p q"
64 $ extra_files="dir/n dir/o p q"
65
65
66 $ hg init reference-repo
66 $ hg init reference-repo
67 $ cd reference-repo
67 $ cd reference-repo
68 $ mkdir -p dir/nested dir2
68 $ mkdir -p dir/nested dir2
69 $ touch -t 200001010000 $first_files $directories
69 $ touch -t 200001010000 $first_files $directories
70 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
70 $ hg commit -Aqm "recreate a bunch of files to facilitate dirstate-v2 append"
71 $ touch -t 200001010010 $second_files $directories
71 $ touch -t 200001010010 $second_files $directories
72 $ hg commit -Aqm "more files to have two commit"
72 $ hg commit -Aqm "more files to have two commit"
73 $ hg log -G -v
73 $ hg log -G -v
74 @ changeset: 1:9a86dcbfb938
74 @ changeset: 1:9a86dcbfb938
75 | tag: tip
75 | tag: tip
76 | user: test
76 | user: test
77 | date: Thu Jan 01 00:00:00 1970 +0000
77 | date: Thu Jan 01 00:00:00 1970 +0000
78 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
78 | files: dir/i dir/j dir/nested/h dir/nested/m dir2/k dir2/l g
79 | description:
79 | description:
80 | more files to have two commit
80 | more files to have two commit
81 |
81 |
82 |
82 |
83 o changeset: 0:4f23db756b09
83 o changeset: 0:4f23db756b09
84 user: test
84 user: test
85 date: Thu Jan 01 00:00:00 1970 +0000
85 date: Thu Jan 01 00:00:00 1970 +0000
86 files: dir/b dir/c dir/d dir/nested/a dir2/e f
86 files: dir/b dir/c dir/d dir/nested/a dir2/e f
87 description:
87 description:
88 recreate a bunch of files to facilitate dirstate-v2 append
88 recreate a bunch of files to facilitate dirstate-v2 append
89
89
90
90
91 $ hg manifest
91 $ hg manifest
92 dir/b
92 dir/b
93 dir/c
93 dir/c
94 dir/d
94 dir/d
95 dir/i
95 dir/i
96 dir/j
96 dir/j
97 dir/nested/a
97 dir/nested/a
98 dir/nested/h
98 dir/nested/h
99 dir/nested/m
99 dir/nested/m
100 dir2/e
100 dir2/e
101 dir2/k
101 dir2/k
102 dir2/l
102 dir2/l
103 f
103 f
104 g
104 g
105
105
106 Add some unknown files and refresh the dirstate
106 Add some unknown files and refresh the dirstate
107
107
108 $ touch -t 200001010020 $extra_files
108 $ touch -t 200001010020 $extra_files
109 $ hg add dir/o
109 $ hg add dir/o
110 $ hg remove dir/nested/m
110 $ hg remove dir/nested/m
111
111
112 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
112 $ hg st --config devel.dirstate.v2.data_update_mode=force-new
113 A dir/o
113 A dir/o
114 R dir/nested/m
114 R dir/nested/m
115 ? dir/n
115 ? dir/n
116 ? p
116 ? p
117 ? q
117 ? q
118 $ hg debugstate
118 $ hg debugstate
119 n 644 0 2000-01-01 00:00:00 dir/b
119 n 644 0 2000-01-01 00:00:00 dir/b
120 n 644 0 2000-01-01 00:00:00 dir/c
120 n 644 0 2000-01-01 00:00:00 dir/c
121 n 644 0 2000-01-01 00:00:00 dir/d
121 n 644 0 2000-01-01 00:00:00 dir/d
122 n 644 0 2000-01-01 00:10:00 dir/i
122 n 644 0 2000-01-01 00:10:00 dir/i
123 n 644 0 2000-01-01 00:10:00 dir/j
123 n 644 0 2000-01-01 00:10:00 dir/j
124 n 644 0 2000-01-01 00:00:00 dir/nested/a
124 n 644 0 2000-01-01 00:00:00 dir/nested/a
125 n 644 0 2000-01-01 00:10:00 dir/nested/h
125 n 644 0 2000-01-01 00:10:00 dir/nested/h
126 r ?????????????????????????????????? dir/nested/m (glob)
126 r ?????????????????????????????????? dir/nested/m (glob)
127 a ?????????????????????????????????? dir/o (glob)
127 a ?????????????????????????????????? dir/o (glob)
128 n 644 0 2000-01-01 00:00:00 dir2/e
128 n 644 0 2000-01-01 00:00:00 dir2/e
129 n 644 0 2000-01-01 00:10:00 dir2/k
129 n 644 0 2000-01-01 00:10:00 dir2/k
130 n 644 0 2000-01-01 00:10:00 dir2/l
130 n 644 0 2000-01-01 00:10:00 dir2/l
131 n 644 0 2000-01-01 00:00:00 f
131 n 644 0 2000-01-01 00:00:00 f
132 n 644 0 2000-01-01 00:10:00 g
132 n 644 0 2000-01-01 00:10:00 g
133 $ hg debugstate > ../reference
133 $ hg debugstate > ../reference
134 $ cd ..
134 $ cd ..
135
135
136 Actual Testing
136 Actual Testing
137 ==============
137 ==============
138
138
139 Race with a `hg add`
139 Race with a `hg add`
140 -------------------
140 -------------------
141
141
142 $ cp -a reference-repo race-with-add
142 $ cp -a reference-repo race-with-add
143 $ cd race-with-add
143 $ cd race-with-add
144
144
145 spin a `hg status` with some caches to update
145 spin a `hg status` with some caches to update
146
146
147 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
147 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
148 > --config rhg.on-unsupported=abort \
148 > --config rhg.on-unsupported=abort \
149 > --config ${cfg}=$TESTTMP/status-race-lock \
149 > --config ${cfg}=$TESTTMP/status-race-lock \
150 > &
150 > &
151 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
151 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
152
152
153 Add a file
153 Add a file
154
154
155 $ hg $d2args add dir/n
155 $ hg $d2args add dir/n
156 $ touch $TESTTMP/status-race-lock
156 $ touch $TESTTMP/status-race-lock
157 $ wait
157 $ wait
158
158
159 The file should in a "added" state
159 The file should in a "added" state
160
160
161 $ hg status
161 $ hg status
162 A dir/n
162 A dir/n
163 A dir/o
163 A dir/o
164 R dir/nested/m
164 R dir/nested/m
165 ? p
165 ? p
166 ? q
166 ? q
167
167
168 The status process should return a consistent result and not crash.
168 The status process should return a consistent result and not crash.
169
169
170 #if dirstate-v1
170 #if dirstate-v1
171 $ cat $TESTTMP/status-race-lock.out
171 $ cat $TESTTMP/status-race-lock.out
172 A dir/n
172 A dir/n
173 A dir/o
173 A dir/o
174 R dir/nested/m
174 R dir/nested/m
175 ? p
175 ? p
176 ? q
176 ? q
177 $ cat $TESTTMP/status-race-lock.log
177 $ cat $TESTTMP/status-race-lock.log
178 #else
178 #else
179 #if rhg
179 #if rhg
180 #if pre-all-read
180 #if pre-all-read
181 $ cat $TESTTMP/status-race-lock.out
181 $ cat $TESTTMP/status-race-lock.out
182 A dir/n
182 A dir/n
183 A dir/o
183 A dir/o
184 R dir/nested/m
184 R dir/nested/m
185 ? p
185 ? p
186 ? q
186 ? q
187 $ cat $TESTTMP/status-race-lock.log
187 $ cat $TESTTMP/status-race-lock.log
188 #else
188 #else
189 #if dirstate-v2-append
189 #if dirstate-v2-append
190 $ cat $TESTTMP/status-race-lock.out
190 $ cat $TESTTMP/status-race-lock.out
191 A dir/o
191 A dir/o
192 R dir/nested/m
192 R dir/nested/m
193 ? dir/n
193 ? dir/n
194 ? p
194 ? p
195 ? q
195 ? q
196 $ cat $TESTTMP/status-race-lock.log
196 $ cat $TESTTMP/status-race-lock.log
197 #else
197 #else
198 $ cat $TESTTMP/status-race-lock.out
198 $ cat $TESTTMP/status-race-lock.out
199 A dir/n
200 A dir/o
201 R dir/nested/m
202 ? p
203 ? q
199 $ cat $TESTTMP/status-race-lock.log
204 $ cat $TESTTMP/status-race-lock.log
200 abort: dirstate-v2 parse error: not enough bytes on disk
201 #endif
205 #endif
202 #endif
206 #endif
203 #else
207 #else
204 #if rust
208 #if rust
205 #if dirstate-v2-rewrite
209 #if dirstate-v2-rewrite
206 $ cat $TESTTMP/status-race-lock.out
210 $ cat $TESTTMP/status-race-lock.out
207 A dir/n
211 A dir/n
208 A dir/o
212 A dir/o
209 R dir/nested/m
213 R dir/nested/m
210 ? p
214 ? p
211 ? q
215 ? q
212 $ cat $TESTTMP/status-race-lock.log
216 $ cat $TESTTMP/status-race-lock.log
213 #else
217 #else
214 $ cat $TESTTMP/status-race-lock.out
218 $ cat $TESTTMP/status-race-lock.out
215 A dir/o
219 A dir/o
216 R dir/nested/m
220 R dir/nested/m
217 ? dir/n
221 ? dir/n
218 ? p
222 ? p
219 ? q
223 ? q
220 $ cat $TESTTMP/status-race-lock.log
224 $ cat $TESTTMP/status-race-lock.log
221 #endif
225 #endif
222 #else
226 #else
223 $ cat $TESTTMP/status-race-lock.out
227 $ cat $TESTTMP/status-race-lock.out
224 A dir/n
228 A dir/n
225 A dir/o
229 A dir/o
226 R dir/nested/m
230 R dir/nested/m
227 ? p
231 ? p
228 ? q
232 ? q
229 $ cat $TESTTMP/status-race-lock.log
233 $ cat $TESTTMP/status-race-lock.log
230 #endif
234 #endif
231 #endif
235 #endif
232 #endif
236 #endif
233
237
234 final cleanup
238 final cleanup
235
239
236 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
240 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
237 $ cd ..
241 $ cd ..
238
242
239 Race with a `hg commit`
243 Race with a `hg commit`
240 -----------------------
244 -----------------------
241
245
242 $ cp -a reference-repo race-with-commit
246 $ cp -a reference-repo race-with-commit
243 $ cd race-with-commit
247 $ cd race-with-commit
244
248
245 spin a `hg status with some cache to update
249 spin a `hg status with some cache to update
246
250
247 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
251 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
248 > --config rhg.on-unsupported=abort \
252 > --config rhg.on-unsupported=abort \
249 > --config ${cfg}=$TESTTMP/status-race-lock \
253 > --config ${cfg}=$TESTTMP/status-race-lock \
250 > &
254 > &
251 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
255 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
252
256
253 Add a do a commit
257 Add a do a commit
254
258
255 $ hg status
259 $ hg status
256 A dir/o
260 A dir/o
257 R dir/nested/m
261 R dir/nested/m
258 ? dir/n
262 ? dir/n
259 ? p
263 ? p
260 ? q
264 ? q
261 $ hg $d2args commit -m 'racing commit'
265 $ hg $d2args commit -m 'racing commit'
262 $ touch $TESTTMP/status-race-lock
266 $ touch $TESTTMP/status-race-lock
263 $ wait
267 $ wait
264
268
265 commit was created, and status is now clean
269 commit was created, and status is now clean
266
270
267 $ hg log -GT '{node|short} {desc}\n'
271 $ hg log -GT '{node|short} {desc}\n'
268 @ 02a67a77ee9b racing commit
272 @ 02a67a77ee9b racing commit
269 |
273 |
270 o 9a86dcbfb938 more files to have two commit
274 o 9a86dcbfb938 more files to have two commit
271 |
275 |
272 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
276 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
273
277
274 $ hg status
278 $ hg status
275 ? dir/n
279 ? dir/n
276 ? p
280 ? p
277 ? q
281 ? q
278
282
279 The status process should return a consistent result and not crash.
283 The status process should return a consistent result and not crash.
280
284
281 #if dirstate-v1
285 #if dirstate-v1
282 $ cat $TESTTMP/status-race-lock.out
286 $ cat $TESTTMP/status-race-lock.out
283 M dir/o (no-rhg !)
287 M dir/o (no-rhg !)
284 ? dir/n
288 ? dir/n
285 ? p
289 ? p
286 ? q
290 ? q
287 $ cat $TESTTMP/status-race-lock.log
291 $ cat $TESTTMP/status-race-lock.log
288 warning: ignoring unknown working parent 02a67a77ee9b! (no-rhg !)
292 warning: ignoring unknown working parent 02a67a77ee9b! (no-rhg !)
289 #else
293 #else
290 #if rhg
294 #if rhg
291 #if pre-all-read
295 #if pre-all-read
292 $ cat $TESTTMP/status-race-lock.out
296 $ cat $TESTTMP/status-race-lock.out
293 ? dir/n
297 ? dir/n
294 ? p
298 ? p
295 ? q
299 ? q
296 $ cat $TESTTMP/status-race-lock.log
300 $ cat $TESTTMP/status-race-lock.log
297 #else
301 #else
298 #if dirstate-v2-append
302 #if dirstate-v2-append
299 $ cat $TESTTMP/status-race-lock.out
303 $ cat $TESTTMP/status-race-lock.out
300 A dir/o
304 A dir/o
301 R dir/nested/m
305 R dir/nested/m
302 ? dir/n
306 ? dir/n
303 ? p
307 ? p
304 ? q
308 ? q
305 $ cat $TESTTMP/status-race-lock.log
309 $ cat $TESTTMP/status-race-lock.log
306 #else
310 #else
307 $ cat $TESTTMP/status-race-lock.out
311 $ cat $TESTTMP/status-race-lock.out
312 ? dir/n
313 ? p
314 ? q
308 $ cat $TESTTMP/status-race-lock.log
315 $ cat $TESTTMP/status-race-lock.log
309 abort: dirstate-v2 parse error: not enough bytes on disk
310 #endif
316 #endif
311 #endif
317 #endif
312 #else
318 #else
313 #if rust
319 #if rust
314 #if dirstate-v2-rewrite
320 #if dirstate-v2-rewrite
315 $ cat $TESTTMP/status-race-lock.out
321 $ cat $TESTTMP/status-race-lock.out
316 M dir/o
322 M dir/o
317 ? dir/n
323 ? dir/n
318 ? p
324 ? p
319 ? q
325 ? q
320 $ cat $TESTTMP/status-race-lock.log
326 $ cat $TESTTMP/status-race-lock.log
321 warning: ignoring unknown working parent 02a67a77ee9b!
327 warning: ignoring unknown working parent 02a67a77ee9b!
322 #else
328 #else
323 $ cat $TESTTMP/status-race-lock.out
329 $ cat $TESTTMP/status-race-lock.out
324 A dir/o
330 A dir/o
325 R dir/nested/m
331 R dir/nested/m
326 ? dir/n
332 ? dir/n
327 ? p
333 ? p
328 ? q
334 ? q
329 $ cat $TESTTMP/status-race-lock.log
335 $ cat $TESTTMP/status-race-lock.log
330 #endif
336 #endif
331 #else
337 #else
332 $ cat $TESTTMP/status-race-lock.out
338 $ cat $TESTTMP/status-race-lock.out
333 M dir/o
339 M dir/o
334 ? dir/n
340 ? dir/n
335 ? p
341 ? p
336 ? q
342 ? q
337 $ cat $TESTTMP/status-race-lock.log
343 $ cat $TESTTMP/status-race-lock.log
338 warning: ignoring unknown working parent 02a67a77ee9b!
344 warning: ignoring unknown working parent 02a67a77ee9b!
339 #endif
345 #endif
340 #endif
346 #endif
341 #endif
347 #endif
342
348
343 final cleanup
349 final cleanup
344
350
345 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
351 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
346 $ cd ..
352 $ cd ..
347
353
348 Race with a `hg update`
354 Race with a `hg update`
349 -----------------------
355 -----------------------
350
356
351 $ cp -a reference-repo race-with-update
357 $ cp -a reference-repo race-with-update
352 $ cd race-with-update
358 $ cd race-with-update
353
359
354 spin a `hg status` with some caches to update
360 spin a `hg status` with some caches to update
355
361
356 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
362 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
357 > --config rhg.on-unsupported=abort \
363 > --config rhg.on-unsupported=abort \
358 > --config ${cfg}=$TESTTMP/status-race-lock \
364 > --config ${cfg}=$TESTTMP/status-race-lock \
359 > &
365 > &
360 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
366 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
361 do an update
367 do an update
362
368
363 $ hg status
369 $ hg status
364 A dir/o
370 A dir/o
365 R dir/nested/m
371 R dir/nested/m
366 ? dir/n
372 ? dir/n
367 ? p
373 ? p
368 ? q
374 ? q
369 $ hg log -GT '{node|short} {desc}\n'
375 $ hg log -GT '{node|short} {desc}\n'
370 @ 9a86dcbfb938 more files to have two commit
376 @ 9a86dcbfb938 more files to have two commit
371 |
377 |
372 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
378 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
373
379
374 $ hg $d2args update --merge ".~1"
380 $ hg $d2args update --merge ".~1"
375 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
381 0 files updated, 0 files merged, 6 files removed, 0 files unresolved
376 $ touch $TESTTMP/status-race-lock
382 $ touch $TESTTMP/status-race-lock
377 $ wait
383 $ wait
378 #if rhg dirstate-v2-append pre-some-read
384 #if rhg dirstate-v2-append pre-some-read
379 $ hg log -GT '{node|short} {desc}\n'
385 $ hg log -GT '{node|short} {desc}\n'
380 @ 9a86dcbfb938 more files to have two commit
386 @ 9a86dcbfb938 more files to have two commit
381 |
387 |
382 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
388 o 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
383
389
384 $ hg status
390 $ hg status
385 A dir/o
391 A dir/o
386 R dir/nested/m
392 R dir/nested/m
387 ! dir/i
393 ! dir/i
388 ! dir/j
394 ! dir/j
389 ! dir/nested/h
395 ! dir/nested/h
390 ! dir2/k
396 ! dir2/k
391 ! dir2/l
397 ! dir2/l
392 ! g
398 ! g
393 ? dir/n
399 ? dir/n
394 ? p
400 ? p
395 ? q
401 ? q
396 #else
402 #else
397 $ hg log -GT '{node|short} {desc}\n'
403 $ hg log -GT '{node|short} {desc}\n'
398 o 9a86dcbfb938 more files to have two commit
404 o 9a86dcbfb938 more files to have two commit
399 |
405 |
400 @ 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
406 @ 4f23db756b09 recreate a bunch of files to facilitate dirstate-v2 append
401
407
402 $ hg status
408 $ hg status
403 A dir/o
409 A dir/o
404 ? dir/n
410 ? dir/n
405 ? p
411 ? p
406 ? q
412 ? q
407 #endif
413 #endif
408
414
409 The status process should return a consistent result and not crash.
415 The status process should return a consistent result and not crash.
410
416
411 #if dirstate-v1
417 #if dirstate-v1
412 $ cat $TESTTMP/status-race-lock.out
418 $ cat $TESTTMP/status-race-lock.out
413 A dir/o
419 A dir/o
414 ? dir/n
420 ? dir/n
415 ? p
421 ? p
416 ? q
422 ? q
417 $ cat $TESTTMP/status-race-lock.log
423 $ cat $TESTTMP/status-race-lock.log
418 #else
424 #else
419 #if rhg
425 #if rhg
420 #if pre-all-read
426 #if pre-all-read
421 $ cat $TESTTMP/status-race-lock.out
427 $ cat $TESTTMP/status-race-lock.out
422 A dir/o
428 A dir/o
423 ? dir/n
429 ? dir/n
424 ? p
430 ? p
425 ? q
431 ? q
426 $ cat $TESTTMP/status-race-lock.log
432 $ cat $TESTTMP/status-race-lock.log
427 #else
433 #else
428 #if dirstate-v2-append
434 #if dirstate-v2-append
429 $ cat $TESTTMP/status-race-lock.out
435 $ cat $TESTTMP/status-race-lock.out
430 A dir/o
436 A dir/o
431 R dir/nested/m
437 R dir/nested/m
432 ! dir/i
438 ! dir/i
433 ! dir/j
439 ! dir/j
434 ! dir/nested/h
440 ! dir/nested/h
435 ! dir2/k
441 ! dir2/k
436 ! dir2/l
442 ! dir2/l
437 ! g
443 ! g
438 ? dir/n
444 ? dir/n
439 ? p
445 ? p
440 ? q
446 ? q
441 $ cat $TESTTMP/status-race-lock.log
447 $ cat $TESTTMP/status-race-lock.log
442 #else
448 #else
443 $ cat $TESTTMP/status-race-lock.out
449 $ cat $TESTTMP/status-race-lock.out
450 A dir/o
451 ? dir/n
452 ? p
453 ? q
444 $ cat $TESTTMP/status-race-lock.log
454 $ cat $TESTTMP/status-race-lock.log
445 abort: dirstate-v2 parse error: not enough bytes on disk
446 #endif
455 #endif
447 #endif
456 #endif
448 #else
457 #else
449 #if rust
458 #if rust
450 #if dirstate-v2-rewrite
459 #if dirstate-v2-rewrite
451 $ cat $TESTTMP/status-race-lock.out
460 $ cat $TESTTMP/status-race-lock.out
452 A dir/o
461 A dir/o
453 ? dir/n
462 ? dir/n
454 ? p
463 ? p
455 ? q
464 ? q
456 $ cat $TESTTMP/status-race-lock.log
465 $ cat $TESTTMP/status-race-lock.log
457 #else
466 #else
458 $ cat $TESTTMP/status-race-lock.out
467 $ cat $TESTTMP/status-race-lock.out
459 A dir/o
468 A dir/o
460 R dir/nested/m
469 R dir/nested/m
461 ! dir/i
470 ! dir/i
462 ! dir/j
471 ! dir/j
463 ! dir/nested/h
472 ! dir/nested/h
464 ! dir2/k
473 ! dir2/k
465 ! dir2/l
474 ! dir2/l
466 ! g
475 ! g
467 ? dir/n
476 ? dir/n
468 ? p
477 ? p
469 ? q
478 ? q
470 $ cat $TESTTMP/status-race-lock.log
479 $ cat $TESTTMP/status-race-lock.log
471 #endif
480 #endif
472 #else
481 #else
473 $ cat $TESTTMP/status-race-lock.out
482 $ cat $TESTTMP/status-race-lock.out
474 A dir/o
483 A dir/o
475 ? dir/n
484 ? dir/n
476 ? p
485 ? p
477 ? q
486 ? q
478 $ cat $TESTTMP/status-race-lock.log
487 $ cat $TESTTMP/status-race-lock.log
479 #endif
488 #endif
480 #endif
489 #endif
481 #endif
490 #endif
482
491
483 final cleanup
492 final cleanup
484
493
485 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
494 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
486 $ cd ..
495 $ cd ..
487
496
488 Race with a cache updating `hg status`
497 Race with a cache updating `hg status`
489 --------------------------------------
498 --------------------------------------
490
499
491 It is interesting to race with "read-only" operation (that still update its cache)
500 It is interesting to race with "read-only" operation (that still update its cache)
492
501
493 $ cp -a reference-repo race-with-status
502 $ cp -a reference-repo race-with-status
494 $ cd race-with-status
503 $ cd race-with-status
495
504
496 spin a `hg status` with some caches to update
505 spin a `hg status` with some caches to update
497
506
498 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
507 $ hg st >$TESTTMP/status-race-lock.out 2>$TESTTMP/status-race-lock.log \
499 > --config rhg.on-unsupported=abort \
508 > --config rhg.on-unsupported=abort \
500 > --config ${cfg}=$TESTTMP/status-race-lock \
509 > --config ${cfg}=$TESTTMP/status-race-lock \
501 > &
510 > &
502 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
511 $ $RUNTESTDIR/testlib/wait-on-file 5 $TESTTMP/status-race-lock.waiting
503 do an update
512 do an update
504
513
505 $ touch -t 200001020006 f
514 $ touch -t 200001020006 f
506 $ hg $d2args status
515 $ hg $d2args status
507 A dir/o
516 A dir/o
508 R dir/nested/m
517 R dir/nested/m
509 ? dir/n
518 ? dir/n
510 ? p
519 ? p
511 ? q
520 ? q
512 $ touch $TESTTMP/status-race-lock
521 $ touch $TESTTMP/status-race-lock
513 $ wait
522 $ wait
514
523
515 The status process should return a consistent result and not crash.
524 The status process should return a consistent result and not crash.
516
525
517 #if dirstate-v1
526 #if dirstate-v1
518 $ cat $TESTTMP/status-race-lock.out
527 $ cat $TESTTMP/status-race-lock.out
519 A dir/o
528 A dir/o
520 R dir/nested/m
529 R dir/nested/m
521 ? dir/n
530 ? dir/n
522 ? p
531 ? p
523 ? q
532 ? q
524 $ cat $TESTTMP/status-race-lock.log
533 $ cat $TESTTMP/status-race-lock.log
525 #else
534 #else
526 #if rhg
535 #if rhg
527 #if pre-all-read
536 #if pre-all-read
528 $ cat $TESTTMP/status-race-lock.out
537 $ cat $TESTTMP/status-race-lock.out
529 A dir/o
538 A dir/o
530 R dir/nested/m
539 R dir/nested/m
531 ? dir/n
540 ? dir/n
532 ? p
541 ? p
533 ? q
542 ? q
534 $ cat $TESTTMP/status-race-lock.log
543 $ cat $TESTTMP/status-race-lock.log
535 #else
544 #else
536 #if dirstate-v2-append
545 #if dirstate-v2-append
537 $ cat $TESTTMP/status-race-lock.out
546 $ cat $TESTTMP/status-race-lock.out
538 A dir/o
547 A dir/o
539 R dir/nested/m
548 R dir/nested/m
540 ? dir/n
549 ? dir/n
541 ? p
550 ? p
542 ? q
551 ? q
543 $ cat $TESTTMP/status-race-lock.log
552 $ cat $TESTTMP/status-race-lock.log
544 #else
553 #else
545 $ cat $TESTTMP/status-race-lock.out
554 $ cat $TESTTMP/status-race-lock.out
555 A dir/o
556 R dir/nested/m
557 ? dir/n
558 ? p
559 ? q
546 $ cat $TESTTMP/status-race-lock.log
560 $ cat $TESTTMP/status-race-lock.log
547 abort: dirstate-v2 parse error: not enough bytes on disk
548 #endif
561 #endif
549 #endif
562 #endif
550 #else
563 #else
551 #if rust
564 #if rust
552 #if dirstate-v2-rewrite
565 #if dirstate-v2-rewrite
553 $ cat $TESTTMP/status-race-lock.out
566 $ cat $TESTTMP/status-race-lock.out
554 A dir/o
567 A dir/o
555 R dir/nested/m
568 R dir/nested/m
556 ? dir/n
569 ? dir/n
557 ? p
570 ? p
558 ? q
571 ? q
559 $ cat $TESTTMP/status-race-lock.log
572 $ cat $TESTTMP/status-race-lock.log
560 #else
573 #else
561 $ cat $TESTTMP/status-race-lock.out
574 $ cat $TESTTMP/status-race-lock.out
562 A dir/o
575 A dir/o
563 R dir/nested/m
576 R dir/nested/m
564 ? dir/n
577 ? dir/n
565 ? p
578 ? p
566 ? q
579 ? q
567 $ cat $TESTTMP/status-race-lock.log
580 $ cat $TESTTMP/status-race-lock.log
568 #endif
581 #endif
569 #else
582 #else
570 $ cat $TESTTMP/status-race-lock.out
583 $ cat $TESTTMP/status-race-lock.out
571 A dir/o
584 A dir/o
572 R dir/nested/m
585 R dir/nested/m
573 ? dir/n
586 ? dir/n
574 ? p
587 ? p
575 ? q
588 ? q
576 $ cat $TESTTMP/status-race-lock.log
589 $ cat $TESTTMP/status-race-lock.log
577 #endif
590 #endif
578 #endif
591 #endif
579 #endif
592 #endif
580
593
581 final cleanup
594 final cleanup
582
595
583 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
596 $ rm $TESTTMP/status-race-lock $TESTTMP/status-race-lock.waiting
584 $ cd ..
597 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now