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 |
|
|
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 |
$ |
|
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 |
$ |
|
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 |
$ |
|
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 |
$ |
|
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 |
$ |
|
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 |
$ |
|
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 |
$ |
|
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