##// END OF EJS Templates
rhg: Don’t make repository path absolute too early...
Simon Sapin -
r47474:97ac588b default
parent child Browse files
Show More
@@ -1,196 +1,201 b''
1 use crate::config::ConfigValueParseError;
1 use crate::config::ConfigValueParseError;
2 use std::fmt;
2 use std::fmt;
3
3
4 /// Common error cases that can happen in many different APIs
4 /// Common error cases that can happen in many different APIs
5 #[derive(Debug, derive_more::From)]
5 #[derive(Debug, derive_more::From)]
6 pub enum HgError {
6 pub enum HgError {
7 IoError {
7 IoError {
8 error: std::io::Error,
8 error: std::io::Error,
9 context: IoErrorContext,
9 context: IoErrorContext,
10 },
10 },
11
11
12 /// A file under `.hg/` normally only written by Mercurial is not in the
12 /// A file under `.hg/` normally only written by Mercurial is not in the
13 /// expected format. This indicates a bug in Mercurial, filesystem
13 /// expected format. This indicates a bug in Mercurial, filesystem
14 /// corruption, or hardware failure.
14 /// corruption, or hardware failure.
15 ///
15 ///
16 /// The given string is a short explanation for users, not intended to be
16 /// The given string is a short explanation for users, not intended to be
17 /// machine-readable.
17 /// machine-readable.
18 CorruptedRepository(String),
18 CorruptedRepository(String),
19
19
20 /// The respository or requested operation involves a feature not
20 /// The respository or requested operation involves a feature not
21 /// supported by the Rust implementation. Falling back to the Python
21 /// supported by the Rust implementation. Falling back to the Python
22 /// implementation may or may not work.
22 /// implementation may or may not work.
23 ///
23 ///
24 /// The given string is a short explanation for users, not intended to be
24 /// The given string is a short explanation for users, not intended to be
25 /// machine-readable.
25 /// machine-readable.
26 UnsupportedFeature(String),
26 UnsupportedFeature(String),
27
27
28 /// Operation cannot proceed for some other reason.
28 /// Operation cannot proceed for some other reason.
29 ///
29 ///
30 /// The given string is a short explanation for users, not intended to be
30 /// The given string is a short explanation for users, not intended to be
31 /// machine-readable.
31 /// machine-readable.
32 Abort(String),
32 Abort(String),
33
33
34 /// A configuration value is not in the expected syntax.
34 /// A configuration value is not in the expected syntax.
35 ///
35 ///
36 /// These errors can happen in many places in the code because values are
36 /// These errors can happen in many places in the code because values are
37 /// parsed lazily as the file-level parser does not know the expected type
37 /// parsed lazily as the file-level parser does not know the expected type
38 /// and syntax of each value.
38 /// and syntax of each value.
39 #[from]
39 #[from]
40 ConfigValueParseError(ConfigValueParseError),
40 ConfigValueParseError(ConfigValueParseError),
41 }
41 }
42
42
43 /// Details about where an I/O error happened
43 /// Details about where an I/O error happened
44 #[derive(Debug)]
44 #[derive(Debug)]
45 pub enum IoErrorContext {
45 pub enum IoErrorContext {
46 ReadingFile(std::path::PathBuf),
46 ReadingFile(std::path::PathBuf),
47 WritingFile(std::path::PathBuf),
47 WritingFile(std::path::PathBuf),
48 RemovingFile(std::path::PathBuf),
48 RemovingFile(std::path::PathBuf),
49 RenamingFile {
49 RenamingFile {
50 from: std::path::PathBuf,
50 from: std::path::PathBuf,
51 to: std::path::PathBuf,
51 to: std::path::PathBuf,
52 },
52 },
53 /// `std::fs::canonicalize`
54 CanonicalizingPath(std::path::PathBuf),
53 /// `std::env::current_dir`
55 /// `std::env::current_dir`
54 CurrentDir,
56 CurrentDir,
55 /// `std::env::current_exe`
57 /// `std::env::current_exe`
56 CurrentExe,
58 CurrentExe,
57 }
59 }
58
60
59 impl HgError {
61 impl HgError {
60 pub fn corrupted(explanation: impl Into<String>) -> Self {
62 pub fn corrupted(explanation: impl Into<String>) -> Self {
61 // TODO: capture a backtrace here and keep it in the error value
63 // TODO: capture a backtrace here and keep it in the error value
62 // to aid debugging?
64 // to aid debugging?
63 // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html
65 // https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html
64 HgError::CorruptedRepository(explanation.into())
66 HgError::CorruptedRepository(explanation.into())
65 }
67 }
66
68
67 pub fn unsupported(explanation: impl Into<String>) -> Self {
69 pub fn unsupported(explanation: impl Into<String>) -> Self {
68 HgError::UnsupportedFeature(explanation.into())
70 HgError::UnsupportedFeature(explanation.into())
69 }
71 }
70 pub fn abort(explanation: impl Into<String>) -> Self {
72 pub fn abort(explanation: impl Into<String>) -> Self {
71 HgError::Abort(explanation.into())
73 HgError::Abort(explanation.into())
72 }
74 }
73 }
75 }
74
76
75 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
77 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
76 impl fmt::Display for HgError {
78 impl fmt::Display for HgError {
77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78 match self {
80 match self {
79 HgError::Abort(explanation) => write!(f, "{}", explanation),
81 HgError::Abort(explanation) => write!(f, "{}", explanation),
80 HgError::IoError { error, context } => {
82 HgError::IoError { error, context } => {
81 write!(f, "abort: {}: {}", context, error)
83 write!(f, "abort: {}: {}", context, error)
82 }
84 }
83 HgError::CorruptedRepository(explanation) => {
85 HgError::CorruptedRepository(explanation) => {
84 write!(f, "abort: {}", explanation)
86 write!(f, "abort: {}", explanation)
85 }
87 }
86 HgError::UnsupportedFeature(explanation) => {
88 HgError::UnsupportedFeature(explanation) => {
87 write!(f, "unsupported feature: {}", explanation)
89 write!(f, "unsupported feature: {}", explanation)
88 }
90 }
89 HgError::ConfigValueParseError(ConfigValueParseError {
91 HgError::ConfigValueParseError(ConfigValueParseError {
90 origin: _,
92 origin: _,
91 line: _,
93 line: _,
92 section,
94 section,
93 item,
95 item,
94 value,
96 value,
95 expected_type,
97 expected_type,
96 }) => {
98 }) => {
97 // TODO: add origin and line number information, here and in
99 // TODO: add origin and line number information, here and in
98 // corresponding python code
100 // corresponding python code
99 write!(
101 write!(
100 f,
102 f,
101 "config error: {}.{} is not a {} ('{}')",
103 "config error: {}.{} is not a {} ('{}')",
102 String::from_utf8_lossy(section),
104 String::from_utf8_lossy(section),
103 String::from_utf8_lossy(item),
105 String::from_utf8_lossy(item),
104 expected_type,
106 expected_type,
105 String::from_utf8_lossy(value)
107 String::from_utf8_lossy(value)
106 )
108 )
107 }
109 }
108 }
110 }
109 }
111 }
110 }
112 }
111
113
112 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
114 // TODO: use `DisplayBytes` instead to show non-Unicode filenames losslessly?
113 impl fmt::Display for IoErrorContext {
115 impl fmt::Display for IoErrorContext {
114 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115 match self {
117 match self {
116 IoErrorContext::ReadingFile(path) => {
118 IoErrorContext::ReadingFile(path) => {
117 write!(f, "when reading {}", path.display())
119 write!(f, "when reading {}", path.display())
118 }
120 }
119 IoErrorContext::WritingFile(path) => {
121 IoErrorContext::WritingFile(path) => {
120 write!(f, "when writing {}", path.display())
122 write!(f, "when writing {}", path.display())
121 }
123 }
122 IoErrorContext::RemovingFile(path) => {
124 IoErrorContext::RemovingFile(path) => {
123 write!(f, "when removing {}", path.display())
125 write!(f, "when removing {}", path.display())
124 }
126 }
125 IoErrorContext::RenamingFile { from, to } => write!(
127 IoErrorContext::RenamingFile { from, to } => write!(
126 f,
128 f,
127 "when renaming {} to {}",
129 "when renaming {} to {}",
128 from.display(),
130 from.display(),
129 to.display()
131 to.display()
130 ),
132 ),
133 IoErrorContext::CanonicalizingPath(path) => {
134 write!(f, "when canonicalizing {}", path.display())
135 }
131 IoErrorContext::CurrentDir => {
136 IoErrorContext::CurrentDir => {
132 write!(f, "error getting current working directory")
137 write!(f, "error getting current working directory")
133 }
138 }
134 IoErrorContext::CurrentExe => {
139 IoErrorContext::CurrentExe => {
135 write!(f, "error getting current executable")
140 write!(f, "error getting current executable")
136 }
141 }
137 }
142 }
138 }
143 }
139 }
144 }
140
145
141 pub trait IoResultExt<T> {
146 pub trait IoResultExt<T> {
142 /// Annotate a possible I/O error as related to a reading a file at the
147 /// Annotate a possible I/O error as related to a reading a file at the
143 /// given path.
148 /// given path.
144 ///
149 ///
145 /// This allows printing something like “File not found when reading
150 /// This allows printing something like “File not found when reading
146 /// example.txt” instead of just “File not found”.
151 /// example.txt” instead of just “File not found”.
147 ///
152 ///
148 /// Converts a `Result` with `std::io::Error` into one with `HgError`.
153 /// Converts a `Result` with `std::io::Error` into one with `HgError`.
149 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError>;
154 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError>;
150
155
151 fn with_context(
156 fn with_context(
152 self,
157 self,
153 context: impl FnOnce() -> IoErrorContext,
158 context: impl FnOnce() -> IoErrorContext,
154 ) -> Result<T, HgError>;
159 ) -> Result<T, HgError>;
155 }
160 }
156
161
157 impl<T> IoResultExt<T> for std::io::Result<T> {
162 impl<T> IoResultExt<T> for std::io::Result<T> {
158 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError> {
163 fn when_reading_file(self, path: &std::path::Path) -> Result<T, HgError> {
159 self.with_context(|| IoErrorContext::ReadingFile(path.to_owned()))
164 self.with_context(|| IoErrorContext::ReadingFile(path.to_owned()))
160 }
165 }
161
166
162 fn with_context(
167 fn with_context(
163 self,
168 self,
164 context: impl FnOnce() -> IoErrorContext,
169 context: impl FnOnce() -> IoErrorContext,
165 ) -> Result<T, HgError> {
170 ) -> Result<T, HgError> {
166 self.map_err(|error| HgError::IoError {
171 self.map_err(|error| HgError::IoError {
167 error,
172 error,
168 context: context(),
173 context: context(),
169 })
174 })
170 }
175 }
171 }
176 }
172
177
173 pub trait HgResultExt<T> {
178 pub trait HgResultExt<T> {
174 /// Handle missing files separately from other I/O error cases.
179 /// Handle missing files separately from other I/O error cases.
175 ///
180 ///
176 /// Wraps the `Ok` type in an `Option`:
181 /// Wraps the `Ok` type in an `Option`:
177 ///
182 ///
178 /// * `Ok(x)` becomes `Ok(Some(x))`
183 /// * `Ok(x)` becomes `Ok(Some(x))`
179 /// * An I/O "not found" error becomes `Ok(None)`
184 /// * An I/O "not found" error becomes `Ok(None)`
180 /// * Other errors are unchanged
185 /// * Other errors are unchanged
181 fn io_not_found_as_none(self) -> Result<Option<T>, HgError>;
186 fn io_not_found_as_none(self) -> Result<Option<T>, HgError>;
182 }
187 }
183
188
184 impl<T> HgResultExt<T> for Result<T, HgError> {
189 impl<T> HgResultExt<T> for Result<T, HgError> {
185 fn io_not_found_as_none(self) -> Result<Option<T>, HgError> {
190 fn io_not_found_as_none(self) -> Result<Option<T>, HgError> {
186 match self {
191 match self {
187 Ok(x) => Ok(Some(x)),
192 Ok(x) => Ok(Some(x)),
188 Err(HgError::IoError { error, .. })
193 Err(HgError::IoError { error, .. })
189 if error.kind() == std::io::ErrorKind::NotFound =>
194 if error.kind() == std::io::ErrorKind::NotFound =>
190 {
195 {
191 Ok(None)
196 Ok(None)
192 }
197 }
193 Err(other_error) => Err(other_error),
198 Err(other_error) => Err(other_error),
194 }
199 }
195 }
200 }
196 }
201 }
@@ -1,268 +1,265 b''
1 use crate::config::{Config, ConfigError, ConfigParseError};
1 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::errors::{HgError, IoErrorContext, IoResultExt};
2 use crate::errors::{HgError, IoErrorContext, IoResultExt};
3 use crate::requirements;
3 use crate::requirements;
4 use crate::utils::files::get_path_from_bytes;
4 use crate::utils::files::get_path_from_bytes;
5 use crate::utils::{current_dir, SliceExt};
5 use crate::utils::SliceExt;
6 use memmap::{Mmap, MmapOptions};
6 use memmap::{Mmap, MmapOptions};
7 use std::collections::HashSet;
7 use std::collections::HashSet;
8 use std::path::{Path, PathBuf};
8 use std::path::{Path, PathBuf};
9
9
10 /// A repository on disk
10 /// A repository on disk
11 pub struct Repo {
11 pub struct Repo {
12 working_directory: PathBuf,
12 working_directory: PathBuf,
13 dot_hg: PathBuf,
13 dot_hg: PathBuf,
14 store: PathBuf,
14 store: PathBuf,
15 requirements: HashSet<String>,
15 requirements: HashSet<String>,
16 config: Config,
16 config: Config,
17 }
17 }
18
18
19 #[derive(Debug, derive_more::From)]
19 #[derive(Debug, derive_more::From)]
20 pub enum RepoError {
20 pub enum RepoError {
21 NotFound {
21 NotFound {
22 at: PathBuf,
22 at: PathBuf,
23 },
23 },
24 #[from]
24 #[from]
25 ConfigParseError(ConfigParseError),
25 ConfigParseError(ConfigParseError),
26 #[from]
26 #[from]
27 Other(HgError),
27 Other(HgError),
28 }
28 }
29
29
30 impl From<ConfigError> for RepoError {
30 impl From<ConfigError> for RepoError {
31 fn from(error: ConfigError) -> Self {
31 fn from(error: ConfigError) -> Self {
32 match error {
32 match error {
33 ConfigError::Parse(error) => error.into(),
33 ConfigError::Parse(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
34 ConfigError::Other(error) => error.into(),
35 }
35 }
36 }
36 }
37 }
37 }
38
38
39 /// Filesystem access abstraction for the contents of a given "base" diretory
39 /// Filesystem access abstraction for the contents of a given "base" diretory
40 #[derive(Clone, Copy)]
40 #[derive(Clone, Copy)]
41 pub struct Vfs<'a> {
41 pub struct Vfs<'a> {
42 pub(crate) base: &'a Path,
42 pub(crate) base: &'a Path,
43 }
43 }
44
44
45 impl Repo {
45 impl Repo {
46 /// Find a repository, either at the given path (which must contain a `.hg`
46 /// Find a repository, either at the given path (which must contain a `.hg`
47 /// sub-directory) or by searching the current directory and its
47 /// sub-directory) or by searching the current directory and its
48 /// ancestors.
48 /// ancestors.
49 ///
49 ///
50 /// A method with two very different "modes" like this usually a code smell
50 /// A method with two very different "modes" like this usually a code smell
51 /// to make two methods instead, but in this case an `Option` is what rhg
51 /// to make two methods instead, but in this case an `Option` is what rhg
52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
52 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
53 /// Having two methods would just move that `if` to almost all callers.
53 /// Having two methods would just move that `if` to almost all callers.
54 pub fn find(
54 pub fn find(
55 config: &Config,
55 config: &Config,
56 explicit_path: Option<&Path>,
56 explicit_path: Option<&Path>,
57 ) -> Result<Self, RepoError> {
57 ) -> Result<Self, RepoError> {
58 if let Some(root) = explicit_path {
58 if let Some(root) = explicit_path {
59 // Having an absolute path isn’t necessary here but can help code
59 if root.join(".hg").is_dir() {
60 // elsewhere
60 Self::new_at_path(root.to_owned(), config)
61 let absolute_root = current_dir()?.join(root);
61 } else if root.is_file() {
62 if absolute_root.join(".hg").is_dir() {
63 Self::new_at_path(absolute_root, config)
64 } else if absolute_root.is_file() {
65 Err(HgError::unsupported("bundle repository").into())
62 Err(HgError::unsupported("bundle repository").into())
66 } else {
63 } else {
67 Err(RepoError::NotFound {
64 Err(RepoError::NotFound {
68 at: root.to_owned(),
65 at: root.to_owned(),
69 })
66 })
70 }
67 }
71 } else {
68 } else {
72 let current_directory = crate::utils::current_dir()?;
69 let current_directory = crate::utils::current_dir()?;
73 // ancestors() is inclusive: it first yields `current_directory`
70 // ancestors() is inclusive: it first yields `current_directory`
74 // as-is.
71 // as-is.
75 for ancestor in current_directory.ancestors() {
72 for ancestor in current_directory.ancestors() {
76 if ancestor.join(".hg").is_dir() {
73 if ancestor.join(".hg").is_dir() {
77 return Self::new_at_path(ancestor.to_owned(), config);
74 return Self::new_at_path(ancestor.to_owned(), config);
78 }
75 }
79 }
76 }
80 Err(RepoError::NotFound {
77 Err(RepoError::NotFound {
81 at: current_directory,
78 at: current_directory,
82 })
79 })
83 }
80 }
84 }
81 }
85
82
86 /// To be called after checking that `.hg` is a sub-directory
83 /// To be called after checking that `.hg` is a sub-directory
87 fn new_at_path(
84 fn new_at_path(
88 working_directory: PathBuf,
85 working_directory: PathBuf,
89 config: &Config,
86 config: &Config,
90 ) -> Result<Self, RepoError> {
87 ) -> Result<Self, RepoError> {
91 let dot_hg = working_directory.join(".hg");
88 let dot_hg = working_directory.join(".hg");
92
89
93 let mut repo_config_files = Vec::new();
90 let mut repo_config_files = Vec::new();
94 repo_config_files.push(dot_hg.join("hgrc"));
91 repo_config_files.push(dot_hg.join("hgrc"));
95 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
92 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
96
93
97 let hg_vfs = Vfs { base: &dot_hg };
94 let hg_vfs = Vfs { base: &dot_hg };
98 let mut reqs = requirements::load_if_exists(hg_vfs)?;
95 let mut reqs = requirements::load_if_exists(hg_vfs)?;
99 let relative =
96 let relative =
100 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
97 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
101 let shared =
98 let shared =
102 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
99 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
103
100
104 // From `mercurial/localrepo.py`:
101 // From `mercurial/localrepo.py`:
105 //
102 //
106 // if .hg/requires contains the sharesafe requirement, it means
103 // if .hg/requires contains the sharesafe requirement, it means
107 // there exists a `.hg/store/requires` too and we should read it
104 // there exists a `.hg/store/requires` too and we should read it
108 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
105 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
109 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
106 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
110 // is not present, refer checkrequirementscompat() for that
107 // is not present, refer checkrequirementscompat() for that
111 //
108 //
112 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
109 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
113 // repository was shared the old way. We check the share source
110 // repository was shared the old way. We check the share source
114 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
111 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
115 // current repository needs to be reshared
112 // current repository needs to be reshared
116 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
113 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
117
114
118 let store_path;
115 let store_path;
119 if !shared {
116 if !shared {
120 store_path = dot_hg.join("store");
117 store_path = dot_hg.join("store");
121 } else {
118 } else {
122 let bytes = hg_vfs.read("sharedpath")?;
119 let bytes = hg_vfs.read("sharedpath")?;
123 let mut shared_path =
120 let mut shared_path =
124 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
121 get_path_from_bytes(bytes.trim_end_newlines()).to_owned();
125 if relative {
122 if relative {
126 shared_path = dot_hg.join(shared_path)
123 shared_path = dot_hg.join(shared_path)
127 }
124 }
128 if !shared_path.is_dir() {
125 if !shared_path.is_dir() {
129 return Err(HgError::corrupted(format!(
126 return Err(HgError::corrupted(format!(
130 ".hg/sharedpath points to nonexistent directory {}",
127 ".hg/sharedpath points to nonexistent directory {}",
131 shared_path.display()
128 shared_path.display()
132 ))
129 ))
133 .into());
130 .into());
134 }
131 }
135
132
136 store_path = shared_path.join("store");
133 store_path = shared_path.join("store");
137
134
138 let source_is_share_safe =
135 let source_is_share_safe =
139 requirements::load(Vfs { base: &shared_path })?
136 requirements::load(Vfs { base: &shared_path })?
140 .contains(requirements::SHARESAFE_REQUIREMENT);
137 .contains(requirements::SHARESAFE_REQUIREMENT);
141
138
142 if share_safe && !source_is_share_safe {
139 if share_safe && !source_is_share_safe {
143 return Err(match config
140 return Err(match config
144 .get(b"share", b"safe-mismatch.source-not-safe")
141 .get(b"share", b"safe-mismatch.source-not-safe")
145 {
142 {
146 Some(b"abort") | None => HgError::abort(
143 Some(b"abort") | None => HgError::abort(
147 "abort: share source does not support share-safe requirement\n\
144 "abort: share source does not support share-safe requirement\n\
148 (see `hg help config.format.use-share-safe` for more information)",
145 (see `hg help config.format.use-share-safe` for more information)",
149 ),
146 ),
150 _ => HgError::unsupported("share-safe downgrade"),
147 _ => HgError::unsupported("share-safe downgrade"),
151 }
148 }
152 .into());
149 .into());
153 } else if source_is_share_safe && !share_safe {
150 } else if source_is_share_safe && !share_safe {
154 return Err(
151 return Err(
155 match config.get(b"share", b"safe-mismatch.source-safe") {
152 match config.get(b"share", b"safe-mismatch.source-safe") {
156 Some(b"abort") | None => HgError::abort(
153 Some(b"abort") | None => HgError::abort(
157 "abort: version mismatch: source uses share-safe \
154 "abort: version mismatch: source uses share-safe \
158 functionality while the current share does not\n\
155 functionality while the current share does not\n\
159 (see `hg help config.format.use-share-safe` for more information)",
156 (see `hg help config.format.use-share-safe` for more information)",
160 ),
157 ),
161 _ => HgError::unsupported("share-safe upgrade"),
158 _ => HgError::unsupported("share-safe upgrade"),
162 }
159 }
163 .into(),
160 .into(),
164 );
161 );
165 }
162 }
166
163
167 if share_safe {
164 if share_safe {
168 repo_config_files.insert(0, shared_path.join("hgrc"))
165 repo_config_files.insert(0, shared_path.join("hgrc"))
169 }
166 }
170 }
167 }
171 if share_safe {
168 if share_safe {
172 reqs.extend(requirements::load(Vfs { base: &store_path })?);
169 reqs.extend(requirements::load(Vfs { base: &store_path })?);
173 }
170 }
174
171
175 let repo_config = config.combine_with_repo(&repo_config_files)?;
172 let repo_config = config.combine_with_repo(&repo_config_files)?;
176
173
177 let repo = Self {
174 let repo = Self {
178 requirements: reqs,
175 requirements: reqs,
179 working_directory,
176 working_directory,
180 store: store_path,
177 store: store_path,
181 dot_hg,
178 dot_hg,
182 config: repo_config,
179 config: repo_config,
183 };
180 };
184
181
185 requirements::check(&repo)?;
182 requirements::check(&repo)?;
186
183
187 Ok(repo)
184 Ok(repo)
188 }
185 }
189
186
190 pub fn working_directory_path(&self) -> &Path {
187 pub fn working_directory_path(&self) -> &Path {
191 &self.working_directory
188 &self.working_directory
192 }
189 }
193
190
194 pub fn requirements(&self) -> &HashSet<String> {
191 pub fn requirements(&self) -> &HashSet<String> {
195 &self.requirements
192 &self.requirements
196 }
193 }
197
194
198 pub fn config(&self) -> &Config {
195 pub fn config(&self) -> &Config {
199 &self.config
196 &self.config
200 }
197 }
201
198
202 /// For accessing repository files (in `.hg`), except for the store
199 /// For accessing repository files (in `.hg`), except for the store
203 /// (`.hg/store`).
200 /// (`.hg/store`).
204 pub fn hg_vfs(&self) -> Vfs<'_> {
201 pub fn hg_vfs(&self) -> Vfs<'_> {
205 Vfs { base: &self.dot_hg }
202 Vfs { base: &self.dot_hg }
206 }
203 }
207
204
208 /// For accessing repository store files (in `.hg/store`)
205 /// For accessing repository store files (in `.hg/store`)
209 pub fn store_vfs(&self) -> Vfs<'_> {
206 pub fn store_vfs(&self) -> Vfs<'_> {
210 Vfs { base: &self.store }
207 Vfs { base: &self.store }
211 }
208 }
212
209
213 /// For accessing the working copy
210 /// For accessing the working copy
214
211
215 // The undescore prefix silences the "never used" warning. Remove before
212 // The undescore prefix silences the "never used" warning. Remove before
216 // using.
213 // using.
217 pub fn _working_directory_vfs(&self) -> Vfs<'_> {
214 pub fn _working_directory_vfs(&self) -> Vfs<'_> {
218 Vfs {
215 Vfs {
219 base: &self.working_directory,
216 base: &self.working_directory,
220 }
217 }
221 }
218 }
222
219
223 pub fn dirstate_parents(
220 pub fn dirstate_parents(
224 &self,
221 &self,
225 ) -> Result<crate::dirstate::DirstateParents, HgError> {
222 ) -> Result<crate::dirstate::DirstateParents, HgError> {
226 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
223 let dirstate = self.hg_vfs().mmap_open("dirstate")?;
227 let parents =
224 let parents =
228 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?;
225 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?;
229 Ok(parents.clone())
226 Ok(parents.clone())
230 }
227 }
231 }
228 }
232
229
233 impl Vfs<'_> {
230 impl Vfs<'_> {
234 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
231 pub fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf {
235 self.base.join(relative_path)
232 self.base.join(relative_path)
236 }
233 }
237
234
238 pub fn read(
235 pub fn read(
239 &self,
236 &self,
240 relative_path: impl AsRef<Path>,
237 relative_path: impl AsRef<Path>,
241 ) -> Result<Vec<u8>, HgError> {
238 ) -> Result<Vec<u8>, HgError> {
242 let path = self.join(relative_path);
239 let path = self.join(relative_path);
243 std::fs::read(&path).when_reading_file(&path)
240 std::fs::read(&path).when_reading_file(&path)
244 }
241 }
245
242
246 pub fn mmap_open(
243 pub fn mmap_open(
247 &self,
244 &self,
248 relative_path: impl AsRef<Path>,
245 relative_path: impl AsRef<Path>,
249 ) -> Result<Mmap, HgError> {
246 ) -> Result<Mmap, HgError> {
250 let path = self.base.join(relative_path);
247 let path = self.base.join(relative_path);
251 let file = std::fs::File::open(&path).when_reading_file(&path)?;
248 let file = std::fs::File::open(&path).when_reading_file(&path)?;
252 // TODO: what are the safety requirements here?
249 // TODO: what are the safety requirements here?
253 let mmap = unsafe { MmapOptions::new().map(&file) }
250 let mmap = unsafe { MmapOptions::new().map(&file) }
254 .when_reading_file(&path)?;
251 .when_reading_file(&path)?;
255 Ok(mmap)
252 Ok(mmap)
256 }
253 }
257
254
258 pub fn rename(
255 pub fn rename(
259 &self,
256 &self,
260 relative_from: impl AsRef<Path>,
257 relative_from: impl AsRef<Path>,
261 relative_to: impl AsRef<Path>,
258 relative_to: impl AsRef<Path>,
262 ) -> Result<(), HgError> {
259 ) -> Result<(), HgError> {
263 let from = self.join(relative_from);
260 let from = self.join(relative_from);
264 let to = self.join(relative_to);
261 let to = self.join(relative_to);
265 std::fs::rename(&from, &to)
262 std::fs::rename(&from, &to)
266 .with_context(|| IoErrorContext::RenamingFile { from, to })
263 .with_context(|| IoErrorContext::RenamingFile { from, to })
267 }
264 }
268 }
265 }
@@ -1,67 +1,69 b''
1 use crate::error::CommandError;
1 use crate::error::CommandError;
2 use clap::Arg;
2 use clap::Arg;
3 use hg::operations::cat;
3 use hg::operations::cat;
4 use hg::utils::hg_path::HgPathBuf;
4 use hg::utils::hg_path::HgPathBuf;
5 use micro_timer::timed;
5 use micro_timer::timed;
6 use std::convert::TryFrom;
6 use std::convert::TryFrom;
7
7
8 pub const HELP_TEXT: &str = "
8 pub const HELP_TEXT: &str = "
9 Output the current or given revision of files
9 Output the current or given revision of files
10 ";
10 ";
11
11
12 pub fn args() -> clap::App<'static, 'static> {
12 pub fn args() -> clap::App<'static, 'static> {
13 clap::SubCommand::with_name("cat")
13 clap::SubCommand::with_name("cat")
14 .arg(
14 .arg(
15 Arg::with_name("rev")
15 Arg::with_name("rev")
16 .help("search the repository as it is in REV")
16 .help("search the repository as it is in REV")
17 .short("-r")
17 .short("-r")
18 .long("--revision")
18 .long("--revision")
19 .value_name("REV")
19 .value_name("REV")
20 .takes_value(true),
20 .takes_value(true),
21 )
21 )
22 .arg(
22 .arg(
23 clap::Arg::with_name("files")
23 clap::Arg::with_name("files")
24 .required(true)
24 .required(true)
25 .multiple(true)
25 .multiple(true)
26 .empty_values(false)
26 .empty_values(false)
27 .value_name("FILE")
27 .value_name("FILE")
28 .help("Activity to start: activity@category"),
28 .help("Activity to start: activity@category"),
29 )
29 )
30 .about(HELP_TEXT)
30 .about(HELP_TEXT)
31 }
31 }
32
32
33 #[timed]
33 #[timed]
34 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
34 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
35 let rev = invocation.subcommand_args.value_of("rev");
35 let rev = invocation.subcommand_args.value_of("rev");
36 let file_args = match invocation.subcommand_args.values_of("files") {
36 let file_args = match invocation.subcommand_args.values_of("files") {
37 Some(files) => files.collect(),
37 Some(files) => files.collect(),
38 None => vec![],
38 None => vec![],
39 };
39 };
40
40
41 let repo = invocation.repo?;
41 let repo = invocation.repo?;
42 let cwd = hg::utils::current_dir()?;
42 let cwd = hg::utils::current_dir()?;
43 let working_directory = repo.working_directory_path();
44 let working_directory = cwd.join(working_directory); // Make it absolute
43
45
44 let mut files = vec![];
46 let mut files = vec![];
45 for file in file_args.iter() {
47 for file in file_args.iter() {
46 // TODO: actually normalize `..` path segments etc?
48 // TODO: actually normalize `..` path segments etc?
47 let normalized = cwd.join(&file);
49 let normalized = cwd.join(&file);
48 let stripped = normalized
50 let stripped = normalized
49 .strip_prefix(&repo.working_directory_path())
51 .strip_prefix(&working_directory)
50 // TODO: error message for path arguments outside of the repo
52 // TODO: error message for path arguments outside of the repo
51 .map_err(|_| CommandError::abort(""))?;
53 .map_err(|_| CommandError::abort(""))?;
52 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
54 let hg_file = HgPathBuf::try_from(stripped.to_path_buf())
53 .map_err(|e| CommandError::abort(e.to_string()))?;
55 .map_err(|e| CommandError::abort(e.to_string()))?;
54 files.push(hg_file);
56 files.push(hg_file);
55 }
57 }
56
58
57 match rev {
59 match rev {
58 Some(rev) => {
60 Some(rev) => {
59 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
61 let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?;
60 invocation.ui.write_stdout(&data)?;
62 invocation.ui.write_stdout(&data)?;
61 Ok(())
63 Ok(())
62 }
64 }
63 None => Err(CommandError::unsupported(
65 None => Err(CommandError::unsupported(
64 "`rhg cat` without `--rev` / `-r`",
66 "`rhg cat` without `--rev` / `-r`",
65 )),
67 )),
66 }
68 }
67 }
69 }
@@ -1,68 +1,71 b''
1 use crate::error::CommandError;
1 use crate::error::CommandError;
2 use crate::ui::Ui;
2 use crate::ui::Ui;
3 use clap::Arg;
3 use clap::Arg;
4 use hg::operations::list_rev_tracked_files;
4 use hg::operations::list_rev_tracked_files;
5 use hg::operations::Dirstate;
5 use hg::operations::Dirstate;
6 use hg::repo::Repo;
6 use hg::repo::Repo;
7 use hg::utils::current_dir;
7 use hg::utils::files::{get_bytes_from_path, relativize_path};
8 use hg::utils::files::{get_bytes_from_path, relativize_path};
8 use hg::utils::hg_path::{HgPath, HgPathBuf};
9 use hg::utils::hg_path::{HgPath, HgPathBuf};
9
10
10 pub const HELP_TEXT: &str = "
11 pub const HELP_TEXT: &str = "
11 List tracked files.
12 List tracked files.
12
13
13 Returns 0 on success.
14 Returns 0 on success.
14 ";
15 ";
15
16
16 pub fn args() -> clap::App<'static, 'static> {
17 pub fn args() -> clap::App<'static, 'static> {
17 clap::SubCommand::with_name("files")
18 clap::SubCommand::with_name("files")
18 .arg(
19 .arg(
19 Arg::with_name("rev")
20 Arg::with_name("rev")
20 .help("search the repository as it is in REV")
21 .help("search the repository as it is in REV")
21 .short("-r")
22 .short("-r")
22 .long("--revision")
23 .long("--revision")
23 .value_name("REV")
24 .value_name("REV")
24 .takes_value(true),
25 .takes_value(true),
25 )
26 )
26 .about(HELP_TEXT)
27 .about(HELP_TEXT)
27 }
28 }
28
29
29 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
30 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
30 let relative = invocation.config.get(b"ui", b"relative-paths");
31 let relative = invocation.config.get(b"ui", b"relative-paths");
31 if relative.is_some() {
32 if relative.is_some() {
32 return Err(CommandError::unsupported(
33 return Err(CommandError::unsupported(
33 "non-default ui.relative-paths",
34 "non-default ui.relative-paths",
34 ));
35 ));
35 }
36 }
36
37
37 let rev = invocation.subcommand_args.value_of("rev");
38 let rev = invocation.subcommand_args.value_of("rev");
38
39
39 let repo = invocation.repo?;
40 let repo = invocation.repo?;
40 if let Some(rev) = rev {
41 if let Some(rev) = rev {
41 let files = list_rev_tracked_files(repo, rev).map_err(|e| (e, rev))?;
42 let files = list_rev_tracked_files(repo, rev).map_err(|e| (e, rev))?;
42 display_files(invocation.ui, repo, files.iter())
43 display_files(invocation.ui, repo, files.iter())
43 } else {
44 } else {
44 let distate = Dirstate::new(repo)?;
45 let distate = Dirstate::new(repo)?;
45 let files = distate.tracked_files()?;
46 let files = distate.tracked_files()?;
46 display_files(invocation.ui, repo, files)
47 display_files(invocation.ui, repo, files)
47 }
48 }
48 }
49 }
49
50
50 fn display_files<'a>(
51 fn display_files<'a>(
51 ui: &Ui,
52 ui: &Ui,
52 repo: &Repo,
53 repo: &Repo,
53 files: impl IntoIterator<Item = &'a HgPath>,
54 files: impl IntoIterator<Item = &'a HgPath>,
54 ) -> Result<(), CommandError> {
55 ) -> Result<(), CommandError> {
55 let cwd = HgPathBuf::from(get_bytes_from_path(hg::utils::current_dir()?));
56 let cwd = HgPathBuf::from(get_bytes_from_path(hg::utils::current_dir()?));
57 let working_directory = repo.working_directory_path();
58 let working_directory = current_dir()?.join(working_directory); // Make it absolute
56 let working_directory =
59 let working_directory =
57 HgPathBuf::from(get_bytes_from_path(repo.working_directory_path()));
60 HgPathBuf::from(get_bytes_from_path(working_directory));
58
61
59 let mut stdout = ui.stdout_buffer();
62 let mut stdout = ui.stdout_buffer();
60
63
61 for file in files {
64 for file in files {
62 let file = working_directory.join(file);
65 let file = working_directory.join(file);
63 stdout.write_all(relativize_path(&file, &cwd).as_ref())?;
66 stdout.write_all(relativize_path(&file, &cwd).as_ref())?;
64 stdout.write_all(b"\n")?;
67 stdout.write_all(b"\n")?;
65 }
68 }
66 stdout.flush()?;
69 stdout.flush()?;
67 Ok(())
70 Ok(())
68 }
71 }
@@ -1,22 +1,28 b''
1 use crate::error::CommandError;
1 use crate::error::CommandError;
2 use format_bytes::format_bytes;
2 use format_bytes::format_bytes;
3 use hg::errors::{IoErrorContext, IoResultExt};
3 use hg::utils::files::get_bytes_from_path;
4 use hg::utils::files::get_bytes_from_path;
4
5
5 pub const HELP_TEXT: &str = "
6 pub const HELP_TEXT: &str = "
6 Print the root directory of the current repository.
7 Print the root directory of the current repository.
7
8
8 Returns 0 on success.
9 Returns 0 on success.
9 ";
10 ";
10
11
11 pub fn args() -> clap::App<'static, 'static> {
12 pub fn args() -> clap::App<'static, 'static> {
12 clap::SubCommand::with_name("root").about(HELP_TEXT)
13 clap::SubCommand::with_name("root").about(HELP_TEXT)
13 }
14 }
14
15
15 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
16 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
16 let repo = invocation.repo?;
17 let repo = invocation.repo?;
17 let bytes = get_bytes_from_path(repo.working_directory_path());
18 let working_directory = repo.working_directory_path();
19 let working_directory = std::fs::canonicalize(working_directory)
20 .with_context(|| {
21 IoErrorContext::CanonicalizingPath(working_directory.to_owned())
22 })?;
23 let bytes = get_bytes_from_path(&working_directory);
18 invocation
24 invocation
19 .ui
25 .ui
20 .write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?;
26 .write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?;
21 Ok(())
27 Ok(())
22 }
28 }
General Comments 0
You need to be logged in to leave comments. Login now