Show More
@@ -1,216 +1,238 b'' | |||||
1 | use crate::config::{Config, ConfigError, ConfigParseError}; |
|
1 | use crate::config::{Config, ConfigError, ConfigParseError}; | |
2 | use crate::errors::{HgError, IoResultExt}; |
|
2 | use crate::errors::{HgError, IoResultExt}; | |
3 | use crate::requirements; |
|
3 | use crate::requirements; | |
|
4 | use crate::utils::current_dir; | |||
4 | use crate::utils::files::get_path_from_bytes; |
|
5 | use crate::utils::files::get_path_from_bytes; | |
5 | use memmap::{Mmap, MmapOptions}; |
|
6 | use memmap::{Mmap, MmapOptions}; | |
6 | use std::collections::HashSet; |
|
7 | use std::collections::HashSet; | |
7 | use std::path::{Path, PathBuf}; |
|
8 | use std::path::{Path, PathBuf}; | |
8 |
|
9 | |||
9 | /// A repository on disk |
|
10 | /// A repository on disk | |
10 | pub struct Repo { |
|
11 | pub struct Repo { | |
11 | working_directory: PathBuf, |
|
12 | working_directory: PathBuf, | |
12 | dot_hg: PathBuf, |
|
13 | dot_hg: PathBuf, | |
13 | store: PathBuf, |
|
14 | store: PathBuf, | |
14 | requirements: HashSet<String>, |
|
15 | requirements: HashSet<String>, | |
15 | config: Config, |
|
16 | config: Config, | |
16 | } |
|
17 | } | |
17 |
|
18 | |||
18 | #[derive(Debug, derive_more::From)] |
|
19 | #[derive(Debug, derive_more::From)] | |
19 | pub enum RepoError { |
|
20 | pub enum RepoError { | |
20 | NotFound { |
|
21 | NotFound { | |
21 |
|
|
22 | at: PathBuf, | |
22 | }, |
|
23 | }, | |
23 | #[from] |
|
24 | #[from] | |
24 | ConfigParseError(ConfigParseError), |
|
25 | ConfigParseError(ConfigParseError), | |
25 | #[from] |
|
26 | #[from] | |
26 | Other(HgError), |
|
27 | Other(HgError), | |
27 | } |
|
28 | } | |
28 |
|
29 | |||
29 | impl From<ConfigError> for RepoError { |
|
30 | impl From<ConfigError> for RepoError { | |
30 | fn from(error: ConfigError) -> Self { |
|
31 | fn from(error: ConfigError) -> Self { | |
31 | match error { |
|
32 | match error { | |
32 | ConfigError::Parse(error) => error.into(), |
|
33 | ConfigError::Parse(error) => error.into(), | |
33 | ConfigError::Other(error) => error.into(), |
|
34 | ConfigError::Other(error) => error.into(), | |
34 | } |
|
35 | } | |
35 | } |
|
36 | } | |
36 | } |
|
37 | } | |
37 |
|
38 | |||
38 | /// Filesystem access abstraction for the contents of a given "base" diretory |
|
39 | /// Filesystem access abstraction for the contents of a given "base" diretory | |
39 | #[derive(Clone, Copy)] |
|
40 | #[derive(Clone, Copy)] | |
40 | pub(crate) struct Vfs<'a> { |
|
41 | pub(crate) struct Vfs<'a> { | |
41 | base: &'a Path, |
|
42 | base: &'a Path, | |
42 | } |
|
43 | } | |
43 |
|
44 | |||
44 | impl Repo { |
|
45 | impl Repo { | |
45 | /// Search the current directory and its ancestores for a repository: |
|
46 | /// Search the current directory and its ancestores for a repository: | |
46 | /// a working directory that contains a `.hg` sub-directory. |
|
47 | /// a working directory that contains a `.hg` sub-directory. | |
47 | pub fn find(config: &Config) -> Result<Self, RepoError> { |
|
48 | /// | |
48 | let current_directory = crate::utils::current_dir()?; |
|
49 | /// `explicit_path` is for `--repository` command-line arguments. | |
49 | // ancestors() is inclusive: it first yields `current_directory` as-is. |
|
50 | pub fn find( | |
50 | for ancestor in current_directory.ancestors() { |
|
51 | config: &Config, | |
51 | if ancestor.join(".hg").is_dir() { |
|
52 | explicit_path: Option<&Path>, | |
52 | return Ok(Self::new_at_path(ancestor.to_owned(), config)?); |
|
53 | ) -> Result<Self, RepoError> { | |
|
54 | if let Some(root) = explicit_path { | |||
|
55 | // Having an absolute path isnβt necessary here but can help code | |||
|
56 | // elsewhere | |||
|
57 | let root = current_dir()?.join(root); | |||
|
58 | if root.join(".hg").is_dir() { | |||
|
59 | Self::new_at_path(root, config) | |||
|
60 | } else { | |||
|
61 | Err(RepoError::NotFound { | |||
|
62 | at: root.to_owned(), | |||
|
63 | }) | |||
53 | } |
|
64 | } | |
|
65 | } else { | |||
|
66 | let current_directory = crate::utils::current_dir()?; | |||
|
67 | // ancestors() is inclusive: it first yields `current_directory` | |||
|
68 | // as-is. | |||
|
69 | for ancestor in current_directory.ancestors() { | |||
|
70 | if ancestor.join(".hg").is_dir() { | |||
|
71 | return Self::new_at_path(ancestor.to_owned(), config); | |||
|
72 | } | |||
|
73 | } | |||
|
74 | Err(RepoError::NotFound { | |||
|
75 | at: current_directory, | |||
|
76 | }) | |||
54 | } |
|
77 | } | |
55 | Err(RepoError::NotFound { current_directory }) |
|
|||
56 | } |
|
78 | } | |
57 |
|
79 | |||
58 | /// To be called after checking that `.hg` is a sub-directory |
|
80 | /// To be called after checking that `.hg` is a sub-directory | |
59 | fn new_at_path( |
|
81 | fn new_at_path( | |
60 | working_directory: PathBuf, |
|
82 | working_directory: PathBuf, | |
61 | config: &Config, |
|
83 | config: &Config, | |
62 | ) -> Result<Self, RepoError> { |
|
84 | ) -> Result<Self, RepoError> { | |
63 | let dot_hg = working_directory.join(".hg"); |
|
85 | let dot_hg = working_directory.join(".hg"); | |
64 |
|
86 | |||
65 | let mut repo_config_files = Vec::new(); |
|
87 | let mut repo_config_files = Vec::new(); | |
66 | repo_config_files.push(dot_hg.join("hgrc")); |
|
88 | repo_config_files.push(dot_hg.join("hgrc")); | |
67 | repo_config_files.push(dot_hg.join("hgrc-not-shared")); |
|
89 | repo_config_files.push(dot_hg.join("hgrc-not-shared")); | |
68 |
|
90 | |||
69 | let hg_vfs = Vfs { base: &dot_hg }; |
|
91 | let hg_vfs = Vfs { base: &dot_hg }; | |
70 | let mut reqs = requirements::load_if_exists(hg_vfs)?; |
|
92 | let mut reqs = requirements::load_if_exists(hg_vfs)?; | |
71 | let relative = |
|
93 | let relative = | |
72 | reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT); |
|
94 | reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT); | |
73 | let shared = |
|
95 | let shared = | |
74 | reqs.contains(requirements::SHARED_REQUIREMENT) || relative; |
|
96 | reqs.contains(requirements::SHARED_REQUIREMENT) || relative; | |
75 |
|
97 | |||
76 | // From `mercurial/localrepo.py`: |
|
98 | // From `mercurial/localrepo.py`: | |
77 | // |
|
99 | // | |
78 | // if .hg/requires contains the sharesafe requirement, it means |
|
100 | // if .hg/requires contains the sharesafe requirement, it means | |
79 | // there exists a `.hg/store/requires` too and we should read it |
|
101 | // there exists a `.hg/store/requires` too and we should read it | |
80 | // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement |
|
102 | // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement | |
81 | // is present. We never write SHARESAFE_REQUIREMENT for a repo if store |
|
103 | // is present. We never write SHARESAFE_REQUIREMENT for a repo if store | |
82 | // is not present, refer checkrequirementscompat() for that |
|
104 | // is not present, refer checkrequirementscompat() for that | |
83 | // |
|
105 | // | |
84 | // However, if SHARESAFE_REQUIREMENT is not present, it means that the |
|
106 | // However, if SHARESAFE_REQUIREMENT is not present, it means that the | |
85 | // repository was shared the old way. We check the share source |
|
107 | // repository was shared the old way. We check the share source | |
86 | // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the |
|
108 | // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the | |
87 | // current repository needs to be reshared |
|
109 | // current repository needs to be reshared | |
88 | let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT); |
|
110 | let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT); | |
89 |
|
111 | |||
90 | let store_path; |
|
112 | let store_path; | |
91 | if !shared { |
|
113 | if !shared { | |
92 | store_path = dot_hg.join("store"); |
|
114 | store_path = dot_hg.join("store"); | |
93 | if share_safe { |
|
115 | if share_safe { | |
94 | reqs.extend(requirements::load(Vfs { base: &store_path })?); |
|
116 | reqs.extend(requirements::load(Vfs { base: &store_path })?); | |
95 | } |
|
117 | } | |
96 | } else { |
|
118 | } else { | |
97 | let bytes = hg_vfs.read("sharedpath")?; |
|
119 | let bytes = hg_vfs.read("sharedpath")?; | |
98 | let mut shared_path = get_path_from_bytes(&bytes).to_owned(); |
|
120 | let mut shared_path = get_path_from_bytes(&bytes).to_owned(); | |
99 | if relative { |
|
121 | if relative { | |
100 | shared_path = dot_hg.join(shared_path) |
|
122 | shared_path = dot_hg.join(shared_path) | |
101 | } |
|
123 | } | |
102 | if !shared_path.is_dir() { |
|
124 | if !shared_path.is_dir() { | |
103 | return Err(HgError::corrupted(format!( |
|
125 | return Err(HgError::corrupted(format!( | |
104 | ".hg/sharedpath points to nonexistent directory {}", |
|
126 | ".hg/sharedpath points to nonexistent directory {}", | |
105 | shared_path.display() |
|
127 | shared_path.display() | |
106 | )) |
|
128 | )) | |
107 | .into()); |
|
129 | .into()); | |
108 | } |
|
130 | } | |
109 |
|
131 | |||
110 | store_path = shared_path.join("store"); |
|
132 | store_path = shared_path.join("store"); | |
111 |
|
133 | |||
112 | let source_is_share_safe = |
|
134 | let source_is_share_safe = | |
113 | requirements::load(Vfs { base: &shared_path })? |
|
135 | requirements::load(Vfs { base: &shared_path })? | |
114 | .contains(requirements::SHARESAFE_REQUIREMENT); |
|
136 | .contains(requirements::SHARESAFE_REQUIREMENT); | |
115 |
|
137 | |||
116 | if share_safe && !source_is_share_safe { |
|
138 | if share_safe && !source_is_share_safe { | |
117 | return Err(match config |
|
139 | return Err(match config | |
118 | .get(b"safe-mismatch", b"source-not-safe") |
|
140 | .get(b"safe-mismatch", b"source-not-safe") | |
119 | { |
|
141 | { | |
120 | Some(b"abort") | None => HgError::abort( |
|
142 | Some(b"abort") | None => HgError::abort( | |
121 | "share source does not support share-safe requirement", |
|
143 | "share source does not support share-safe requirement", | |
122 | ), |
|
144 | ), | |
123 | _ => HgError::unsupported("share-safe downgrade"), |
|
145 | _ => HgError::unsupported("share-safe downgrade"), | |
124 | } |
|
146 | } | |
125 | .into()); |
|
147 | .into()); | |
126 | } else if source_is_share_safe && !share_safe { |
|
148 | } else if source_is_share_safe && !share_safe { | |
127 | return Err( |
|
149 | return Err( | |
128 | match config.get(b"safe-mismatch", b"source-safe") { |
|
150 | match config.get(b"safe-mismatch", b"source-safe") { | |
129 | Some(b"abort") | None => HgError::abort( |
|
151 | Some(b"abort") | None => HgError::abort( | |
130 | "version mismatch: source uses share-safe \ |
|
152 | "version mismatch: source uses share-safe \ | |
131 | functionality while the current share does not", |
|
153 | functionality while the current share does not", | |
132 | ), |
|
154 | ), | |
133 | _ => HgError::unsupported("share-safe upgrade"), |
|
155 | _ => HgError::unsupported("share-safe upgrade"), | |
134 | } |
|
156 | } | |
135 | .into(), |
|
157 | .into(), | |
136 | ); |
|
158 | ); | |
137 | } |
|
159 | } | |
138 |
|
160 | |||
139 | if share_safe { |
|
161 | if share_safe { | |
140 | repo_config_files.insert(0, shared_path.join("hgrc")) |
|
162 | repo_config_files.insert(0, shared_path.join("hgrc")) | |
141 | } |
|
163 | } | |
142 | } |
|
164 | } | |
143 |
|
165 | |||
144 | let repo_config = config.combine_with_repo(&repo_config_files)?; |
|
166 | let repo_config = config.combine_with_repo(&repo_config_files)?; | |
145 |
|
167 | |||
146 | let repo = Self { |
|
168 | let repo = Self { | |
147 | requirements: reqs, |
|
169 | requirements: reqs, | |
148 | working_directory, |
|
170 | working_directory, | |
149 | store: store_path, |
|
171 | store: store_path, | |
150 | dot_hg, |
|
172 | dot_hg, | |
151 | config: repo_config, |
|
173 | config: repo_config, | |
152 | }; |
|
174 | }; | |
153 |
|
175 | |||
154 | requirements::check(&repo)?; |
|
176 | requirements::check(&repo)?; | |
155 |
|
177 | |||
156 | Ok(repo) |
|
178 | Ok(repo) | |
157 | } |
|
179 | } | |
158 |
|
180 | |||
159 | pub fn working_directory_path(&self) -> &Path { |
|
181 | pub fn working_directory_path(&self) -> &Path { | |
160 | &self.working_directory |
|
182 | &self.working_directory | |
161 | } |
|
183 | } | |
162 |
|
184 | |||
163 | pub fn requirements(&self) -> &HashSet<String> { |
|
185 | pub fn requirements(&self) -> &HashSet<String> { | |
164 | &self.requirements |
|
186 | &self.requirements | |
165 | } |
|
187 | } | |
166 |
|
188 | |||
167 | pub fn config(&self) -> &Config { |
|
189 | pub fn config(&self) -> &Config { | |
168 | &self.config |
|
190 | &self.config | |
169 | } |
|
191 | } | |
170 |
|
192 | |||
171 | /// For accessing repository files (in `.hg`), except for the store |
|
193 | /// For accessing repository files (in `.hg`), except for the store | |
172 | /// (`.hg/store`). |
|
194 | /// (`.hg/store`). | |
173 | pub(crate) fn hg_vfs(&self) -> Vfs<'_> { |
|
195 | pub(crate) fn hg_vfs(&self) -> Vfs<'_> { | |
174 | Vfs { base: &self.dot_hg } |
|
196 | Vfs { base: &self.dot_hg } | |
175 | } |
|
197 | } | |
176 |
|
198 | |||
177 | /// For accessing repository store files (in `.hg/store`) |
|
199 | /// For accessing repository store files (in `.hg/store`) | |
178 | pub(crate) fn store_vfs(&self) -> Vfs<'_> { |
|
200 | pub(crate) fn store_vfs(&self) -> Vfs<'_> { | |
179 | Vfs { base: &self.store } |
|
201 | Vfs { base: &self.store } | |
180 | } |
|
202 | } | |
181 |
|
203 | |||
182 | /// For accessing the working copy |
|
204 | /// For accessing the working copy | |
183 |
|
205 | |||
184 | // The undescore prefix silences the "never used" warning. Remove before |
|
206 | // The undescore prefix silences the "never used" warning. Remove before | |
185 | // using. |
|
207 | // using. | |
186 | pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> { |
|
208 | pub(crate) fn _working_directory_vfs(&self) -> Vfs<'_> { | |
187 | Vfs { |
|
209 | Vfs { | |
188 | base: &self.working_directory, |
|
210 | base: &self.working_directory, | |
189 | } |
|
211 | } | |
190 | } |
|
212 | } | |
191 | } |
|
213 | } | |
192 |
|
214 | |||
193 | impl Vfs<'_> { |
|
215 | impl Vfs<'_> { | |
194 | pub(crate) fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf { |
|
216 | pub(crate) fn join(&self, relative_path: impl AsRef<Path>) -> PathBuf { | |
195 | self.base.join(relative_path) |
|
217 | self.base.join(relative_path) | |
196 | } |
|
218 | } | |
197 |
|
219 | |||
198 | pub(crate) fn read( |
|
220 | pub(crate) fn read( | |
199 | &self, |
|
221 | &self, | |
200 | relative_path: impl AsRef<Path>, |
|
222 | relative_path: impl AsRef<Path>, | |
201 | ) -> Result<Vec<u8>, HgError> { |
|
223 | ) -> Result<Vec<u8>, HgError> { | |
202 | let path = self.join(relative_path); |
|
224 | let path = self.join(relative_path); | |
203 | std::fs::read(&path).for_file(&path) |
|
225 | std::fs::read(&path).for_file(&path) | |
204 | } |
|
226 | } | |
205 |
|
227 | |||
206 | pub(crate) fn mmap_open( |
|
228 | pub(crate) fn mmap_open( | |
207 | &self, |
|
229 | &self, | |
208 | relative_path: impl AsRef<Path>, |
|
230 | relative_path: impl AsRef<Path>, | |
209 | ) -> Result<Mmap, HgError> { |
|
231 | ) -> Result<Mmap, HgError> { | |
210 | let path = self.base.join(relative_path); |
|
232 | let path = self.base.join(relative_path); | |
211 | let file = std::fs::File::open(&path).for_file(&path)?; |
|
233 | let file = std::fs::File::open(&path).for_file(&path)?; | |
212 | // TODO: what are the safety requirements here? |
|
234 | // TODO: what are the safety requirements here? | |
213 | let mmap = unsafe { MmapOptions::new().map(&file) }.for_file(&path)?; |
|
235 | let mmap = unsafe { MmapOptions::new().map(&file) }.for_file(&path)?; | |
214 | Ok(mmap) |
|
236 | Ok(mmap) | |
215 | } |
|
237 | } | |
216 | } |
|
238 | } |
@@ -1,73 +1,75 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 clap::ArgMatches; |
|
4 | use clap::ArgMatches; | |
5 | use hg::config::Config; |
|
5 | use hg::config::Config; | |
6 | use hg::operations::cat; |
|
6 | use hg::operations::cat; | |
7 | use hg::repo::Repo; |
|
7 | use hg::repo::Repo; | |
8 | use hg::utils::hg_path::HgPathBuf; |
|
8 | use hg::utils::hg_path::HgPathBuf; | |
9 | use micro_timer::timed; |
|
9 | use micro_timer::timed; | |
10 | use std::convert::TryFrom; |
|
10 | use std::convert::TryFrom; | |
|
11 | use std::path::Path; | |||
11 |
|
12 | |||
12 | pub const HELP_TEXT: &str = " |
|
13 | pub const HELP_TEXT: &str = " | |
13 | Output the current or given revision of files |
|
14 | Output the current or given revision of files | |
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("cat") |
|
18 | clap::SubCommand::with_name("cat") | |
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 | .arg( |
|
27 | .arg( | |
27 | clap::Arg::with_name("files") |
|
28 | clap::Arg::with_name("files") | |
28 | .required(true) |
|
29 | .required(true) | |
29 | .multiple(true) |
|
30 | .multiple(true) | |
30 | .empty_values(false) |
|
31 | .empty_values(false) | |
31 | .value_name("FILE") |
|
32 | .value_name("FILE") | |
32 | .help("Activity to start: activity@category"), |
|
33 | .help("Activity to start: activity@category"), | |
33 | ) |
|
34 | ) | |
34 | .about(HELP_TEXT) |
|
35 | .about(HELP_TEXT) | |
35 | } |
|
36 | } | |
36 |
|
37 | |||
37 | #[timed] |
|
38 | #[timed] | |
38 | pub fn run( |
|
39 | pub fn run( | |
39 | ui: &Ui, |
|
40 | ui: &Ui, | |
40 | config: &Config, |
|
41 | config: &Config, | |
|
42 | repo_path: Option<&Path>, | |||
41 | args: &ArgMatches, |
|
43 | args: &ArgMatches, | |
42 | ) -> Result<(), CommandError> { |
|
44 | ) -> Result<(), CommandError> { | |
43 | let rev = args.value_of("rev"); |
|
45 | let rev = args.value_of("rev"); | |
44 | let file_args = match args.values_of("files") { |
|
46 | let file_args = match args.values_of("files") { | |
45 | Some(files) => files.collect(), |
|
47 | Some(files) => files.collect(), | |
46 | None => vec![], |
|
48 | None => vec![], | |
47 | }; |
|
49 | }; | |
48 |
|
50 | |||
49 | let repo = Repo::find(config)?; |
|
51 | let repo = Repo::find(config, repo_path)?; | |
50 | let cwd = hg::utils::current_dir()?; |
|
52 | let cwd = hg::utils::current_dir()?; | |
51 |
|
53 | |||
52 | let mut files = vec![]; |
|
54 | let mut files = vec![]; | |
53 | for file in file_args.iter() { |
|
55 | for file in file_args.iter() { | |
54 | // TODO: actually normalize `..` path segments etc? |
|
56 | // TODO: actually normalize `..` path segments etc? | |
55 | let normalized = cwd.join(&file); |
|
57 | let normalized = cwd.join(&file); | |
56 | let stripped = normalized |
|
58 | let stripped = normalized | |
57 | .strip_prefix(&repo.working_directory_path()) |
|
59 | .strip_prefix(&repo.working_directory_path()) | |
58 | // TODO: error message for path arguments outside of the repo |
|
60 | // TODO: error message for path arguments outside of the repo | |
59 | .map_err(|_| CommandError::abort(""))?; |
|
61 | .map_err(|_| CommandError::abort(""))?; | |
60 | let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) |
|
62 | let hg_file = HgPathBuf::try_from(stripped.to_path_buf()) | |
61 | .map_err(|e| CommandError::abort(e.to_string()))?; |
|
63 | .map_err(|e| CommandError::abort(e.to_string()))?; | |
62 | files.push(hg_file); |
|
64 | files.push(hg_file); | |
63 | } |
|
65 | } | |
64 |
|
66 | |||
65 | match rev { |
|
67 | match rev { | |
66 | Some(rev) => { |
|
68 | Some(rev) => { | |
67 | let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?; |
|
69 | let data = cat(&repo, rev, &files).map_err(|e| (e, rev))?; | |
68 | ui.write_stdout(&data)?; |
|
70 | ui.write_stdout(&data)?; | |
69 | Ok(()) |
|
71 | Ok(()) | |
70 | } |
|
72 | } | |
71 | None => Err(CommandError::Unimplemented.into()), |
|
73 | None => Err(CommandError::Unimplemented.into()), | |
72 | } |
|
74 | } | |
73 | } |
|
75 | } |
@@ -1,72 +1,74 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 clap::ArgGroup; |
|
4 | use clap::ArgGroup; | |
5 | use clap::ArgMatches; |
|
5 | use clap::ArgMatches; | |
6 | use hg::config::Config; |
|
6 | use hg::config::Config; | |
7 | use hg::operations::{debug_data, DebugDataKind}; |
|
7 | use hg::operations::{debug_data, DebugDataKind}; | |
8 | use hg::repo::Repo; |
|
8 | use hg::repo::Repo; | |
9 | use micro_timer::timed; |
|
9 | use micro_timer::timed; | |
|
10 | use std::path::Path; | |||
10 |
|
11 | |||
11 | pub const HELP_TEXT: &str = " |
|
12 | pub const HELP_TEXT: &str = " | |
12 | Dump the contents of a data file revision |
|
13 | Dump the contents of a data file revision | |
13 | "; |
|
14 | "; | |
14 |
|
15 | |||
15 | pub fn args() -> clap::App<'static, 'static> { |
|
16 | pub fn args() -> clap::App<'static, 'static> { | |
16 | clap::SubCommand::with_name("debugdata") |
|
17 | clap::SubCommand::with_name("debugdata") | |
17 | .arg( |
|
18 | .arg( | |
18 | Arg::with_name("changelog") |
|
19 | Arg::with_name("changelog") | |
19 | .help("open changelog") |
|
20 | .help("open changelog") | |
20 | .short("-c") |
|
21 | .short("-c") | |
21 | .long("--changelog"), |
|
22 | .long("--changelog"), | |
22 | ) |
|
23 | ) | |
23 | .arg( |
|
24 | .arg( | |
24 | Arg::with_name("manifest") |
|
25 | Arg::with_name("manifest") | |
25 | .help("open manifest") |
|
26 | .help("open manifest") | |
26 | .short("-m") |
|
27 | .short("-m") | |
27 | .long("--manifest"), |
|
28 | .long("--manifest"), | |
28 | ) |
|
29 | ) | |
29 | .group( |
|
30 | .group( | |
30 | ArgGroup::with_name("") |
|
31 | ArgGroup::with_name("") | |
31 | .args(&["changelog", "manifest"]) |
|
32 | .args(&["changelog", "manifest"]) | |
32 | .required(true), |
|
33 | .required(true), | |
33 | ) |
|
34 | ) | |
34 | .arg( |
|
35 | .arg( | |
35 | Arg::with_name("rev") |
|
36 | Arg::with_name("rev") | |
36 | .help("revision") |
|
37 | .help("revision") | |
37 | .required(true) |
|
38 | .required(true) | |
38 | .value_name("REV"), |
|
39 | .value_name("REV"), | |
39 | ) |
|
40 | ) | |
40 | .about(HELP_TEXT) |
|
41 | .about(HELP_TEXT) | |
41 | } |
|
42 | } | |
42 |
|
43 | |||
43 | #[timed] |
|
44 | #[timed] | |
44 | pub fn run( |
|
45 | pub fn run( | |
45 | ui: &Ui, |
|
46 | ui: &Ui, | |
46 | config: &Config, |
|
47 | config: &Config, | |
|
48 | repo_path: Option<&Path>, | |||
47 | args: &ArgMatches, |
|
49 | args: &ArgMatches, | |
48 | ) -> Result<(), CommandError> { |
|
50 | ) -> Result<(), CommandError> { | |
49 | let rev = args |
|
51 | let rev = args | |
50 | .value_of("rev") |
|
52 | .value_of("rev") | |
51 | .expect("rev should be a required argument"); |
|
53 | .expect("rev should be a required argument"); | |
52 | let kind = |
|
54 | let kind = | |
53 | match (args.is_present("changelog"), args.is_present("manifest")) { |
|
55 | match (args.is_present("changelog"), args.is_present("manifest")) { | |
54 | (true, false) => DebugDataKind::Changelog, |
|
56 | (true, false) => DebugDataKind::Changelog, | |
55 | (false, true) => DebugDataKind::Manifest, |
|
57 | (false, true) => DebugDataKind::Manifest, | |
56 | (true, true) => { |
|
58 | (true, true) => { | |
57 | unreachable!("Should not happen since options are exclusive") |
|
59 | unreachable!("Should not happen since options are exclusive") | |
58 | } |
|
60 | } | |
59 | (false, false) => { |
|
61 | (false, false) => { | |
60 | unreachable!("Should not happen since options are required") |
|
62 | unreachable!("Should not happen since options are required") | |
61 | } |
|
63 | } | |
62 | }; |
|
64 | }; | |
63 |
|
65 | |||
64 | let repo = Repo::find(config)?; |
|
66 | let repo = Repo::find(config, repo_path)?; | |
65 | let data = debug_data(&repo, rev, kind).map_err(|e| (e, rev))?; |
|
67 | let data = debug_data(&repo, rev, kind).map_err(|e| (e, rev))?; | |
66 |
|
68 | |||
67 | let mut stdout = ui.stdout_buffer(); |
|
69 | let mut stdout = ui.stdout_buffer(); | |
68 | stdout.write_all(&data)?; |
|
70 | stdout.write_all(&data)?; | |
69 | stdout.flush()?; |
|
71 | stdout.flush()?; | |
70 |
|
72 | |||
71 | Ok(()) |
|
73 | Ok(()) | |
72 | } |
|
74 | } |
@@ -1,30 +1,32 b'' | |||||
1 | use crate::error::CommandError; |
|
1 | use crate::error::CommandError; | |
2 | use crate::ui::Ui; |
|
2 | use crate::ui::Ui; | |
3 | use clap::ArgMatches; |
|
3 | use clap::ArgMatches; | |
4 | use hg::config::Config; |
|
4 | use hg::config::Config; | |
5 | use hg::repo::Repo; |
|
5 | use hg::repo::Repo; | |
|
6 | use std::path::Path; | |||
6 |
|
7 | |||
7 | pub const HELP_TEXT: &str = " |
|
8 | pub const HELP_TEXT: &str = " | |
8 | Print the current repo requirements. |
|
9 | Print the current repo requirements. | |
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("debugrequirements").about(HELP_TEXT) |
|
13 | clap::SubCommand::with_name("debugrequirements").about(HELP_TEXT) | |
13 | } |
|
14 | } | |
14 |
|
15 | |||
15 | pub fn run( |
|
16 | pub fn run( | |
16 | ui: &Ui, |
|
17 | ui: &Ui, | |
17 | config: &Config, |
|
18 | config: &Config, | |
|
19 | repo_path: Option<&Path>, | |||
18 | _args: &ArgMatches, |
|
20 | _args: &ArgMatches, | |
19 | ) -> Result<(), CommandError> { |
|
21 | ) -> Result<(), CommandError> { | |
20 | let repo = Repo::find(config)?; |
|
22 | let repo = Repo::find(config, repo_path)?; | |
21 | let mut output = String::new(); |
|
23 | let mut output = String::new(); | |
22 | let mut requirements: Vec<_> = repo.requirements().iter().collect(); |
|
24 | let mut requirements: Vec<_> = repo.requirements().iter().collect(); | |
23 | requirements.sort(); |
|
25 | requirements.sort(); | |
24 | for req in requirements { |
|
26 | for req in requirements { | |
25 | output.push_str(req); |
|
27 | output.push_str(req); | |
26 | output.push('\n'); |
|
28 | output.push('\n'); | |
27 | } |
|
29 | } | |
28 | ui.write_stdout(output.as_bytes())?; |
|
30 | ui.write_stdout(output.as_bytes())?; | |
29 | Ok(()) |
|
31 | Ok(()) | |
30 | } |
|
32 | } |
@@ -1,69 +1,70 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 clap::ArgMatches; |
|
4 | use clap::ArgMatches; | |
5 | use hg::config::Config; |
|
5 | use hg::config::Config; | |
6 | use hg::operations::list_rev_tracked_files; |
|
6 | use hg::operations::list_rev_tracked_files; | |
7 | use hg::operations::Dirstate; |
|
7 | use hg::operations::Dirstate; | |
8 | use hg::repo::Repo; |
|
8 | use hg::repo::Repo; | |
9 | use hg::utils::files::{get_bytes_from_path, relativize_path}; |
|
9 | use hg::utils::files::{get_bytes_from_path, relativize_path}; | |
10 | use hg::utils::hg_path::{HgPath, HgPathBuf}; |
|
10 | use hg::utils::hg_path::{HgPath, HgPathBuf}; | |
|
11 | use std::path::Path; | |||
11 |
|
12 | |||
12 | pub const HELP_TEXT: &str = " |
|
13 | pub const HELP_TEXT: &str = " | |
13 | List tracked files. |
|
14 | List tracked files. | |
14 |
|
15 | |||
15 | Returns 0 on success. |
|
16 | Returns 0 on success. | |
16 | "; |
|
17 | "; | |
17 |
|
18 | |||
18 | pub fn args() -> clap::App<'static, 'static> { |
|
19 | pub fn args() -> clap::App<'static, 'static> { | |
19 | clap::SubCommand::with_name("files") |
|
20 | clap::SubCommand::with_name("files") | |
20 | .arg( |
|
21 | .arg( | |
21 | Arg::with_name("rev") |
|
22 | Arg::with_name("rev") | |
22 | .help("search the repository as it is in REV") |
|
23 | .help("search the repository as it is in REV") | |
23 | .short("-r") |
|
24 | .short("-r") | |
24 | .long("--revision") |
|
25 | .long("--revision") | |
25 | .value_name("REV") |
|
26 | .value_name("REV") | |
26 | .takes_value(true), |
|
27 | .takes_value(true), | |
27 | ) |
|
28 | ) | |
28 | .about(HELP_TEXT) |
|
29 | .about(HELP_TEXT) | |
29 | } |
|
30 | } | |
30 |
|
31 | |||
31 | pub fn run( |
|
32 | pub fn run( | |
32 | ui: &Ui, |
|
33 | ui: &Ui, | |
33 | config: &Config, |
|
34 | config: &Config, | |
|
35 | repo_path: Option<&Path>, | |||
34 | args: &ArgMatches, |
|
36 | args: &ArgMatches, | |
35 | ) -> Result<(), CommandError> { |
|
37 | ) -> Result<(), CommandError> { | |
36 | let rev = args.value_of("rev"); |
|
38 | let rev = args.value_of("rev"); | |
37 |
|
39 | |||
38 | let repo = Repo::find(config)?; |
|
40 | let repo = Repo::find(config, repo_path)?; | |
39 | if let Some(rev) = rev { |
|
41 | if let Some(rev) = rev { | |
40 | let files = |
|
42 | let files = | |
41 | list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?; |
|
43 | list_rev_tracked_files(&repo, rev).map_err(|e| (e, rev))?; | |
42 | display_files(ui, &repo, files.iter()) |
|
44 | display_files(ui, &repo, files.iter()) | |
43 | } else { |
|
45 | } else { | |
44 | let distate = Dirstate::new(&repo)?; |
|
46 | let distate = Dirstate::new(&repo)?; | |
45 | let files = distate.tracked_files()?; |
|
47 | let files = distate.tracked_files()?; | |
46 | display_files(ui, &repo, files) |
|
48 | display_files(ui, &repo, files) | |
47 | } |
|
49 | } | |
48 | } |
|
50 | } | |
49 |
|
51 | |||
50 | fn display_files<'a>( |
|
52 | fn display_files<'a>( | |
51 | ui: &Ui, |
|
53 | ui: &Ui, | |
52 | repo: &Repo, |
|
54 | repo: &Repo, | |
53 | files: impl IntoIterator<Item = &'a HgPath>, |
|
55 | files: impl IntoIterator<Item = &'a HgPath>, | |
54 | ) -> Result<(), CommandError> { |
|
56 | ) -> Result<(), CommandError> { | |
55 | let cwd = hg::utils::current_dir()?; |
|
57 | let cwd = HgPathBuf::from(get_bytes_from_path(hg::utils::current_dir()?)); | |
56 | let rooted_cwd = cwd |
|
58 | let working_directory = | |
57 |
|
|
59 | HgPathBuf::from(get_bytes_from_path(repo.working_directory_path())); | |
58 | .expect("cwd was already checked within the repository"); |
|
|||
59 | let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd)); |
|
|||
60 |
|
60 | |||
61 | let mut stdout = ui.stdout_buffer(); |
|
61 | let mut stdout = ui.stdout_buffer(); | |
62 |
|
62 | |||
63 | for file in files { |
|
63 | for file in files { | |
64 | stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?; |
|
64 | let file = working_directory.join(file); | |
|
65 | stdout.write_all(relativize_path(&file, &cwd).as_ref())?; | |||
65 | stdout.write_all(b"\n")?; |
|
66 | stdout.write_all(b"\n")?; | |
66 | } |
|
67 | } | |
67 | stdout.flush()?; |
|
68 | stdout.flush()?; | |
68 | Ok(()) |
|
69 | Ok(()) | |
69 | } |
|
70 | } |
@@ -1,28 +1,30 b'' | |||||
1 | use crate::error::CommandError; |
|
1 | use crate::error::CommandError; | |
2 | use crate::ui::Ui; |
|
2 | use crate::ui::Ui; | |
3 | use clap::ArgMatches; |
|
3 | use clap::ArgMatches; | |
4 | use format_bytes::format_bytes; |
|
4 | use format_bytes::format_bytes; | |
5 | use hg::config::Config; |
|
5 | use hg::config::Config; | |
6 | use hg::repo::Repo; |
|
6 | use hg::repo::Repo; | |
7 | use hg::utils::files::get_bytes_from_path; |
|
7 | use hg::utils::files::get_bytes_from_path; | |
|
8 | use std::path::Path; | |||
8 |
|
9 | |||
9 | pub const HELP_TEXT: &str = " |
|
10 | pub const HELP_TEXT: &str = " | |
10 | Print the root directory of the current repository. |
|
11 | Print the root directory of the current repository. | |
11 |
|
12 | |||
12 | Returns 0 on success. |
|
13 | Returns 0 on success. | |
13 | "; |
|
14 | "; | |
14 |
|
15 | |||
15 | pub fn args() -> clap::App<'static, 'static> { |
|
16 | pub fn args() -> clap::App<'static, 'static> { | |
16 | clap::SubCommand::with_name("root").about(HELP_TEXT) |
|
17 | clap::SubCommand::with_name("root").about(HELP_TEXT) | |
17 | } |
|
18 | } | |
18 |
|
19 | |||
19 | pub fn run( |
|
20 | pub fn run( | |
20 | ui: &Ui, |
|
21 | ui: &Ui, | |
21 | config: &Config, |
|
22 | config: &Config, | |
|
23 | repo_path: Option<&Path>, | |||
22 | _args: &ArgMatches, |
|
24 | _args: &ArgMatches, | |
23 | ) -> Result<(), CommandError> { |
|
25 | ) -> Result<(), CommandError> { | |
24 | let repo = Repo::find(config)?; |
|
26 | let repo = Repo::find(config, repo_path)?; | |
25 | let bytes = get_bytes_from_path(repo.working_directory_path()); |
|
27 | let bytes = get_bytes_from_path(repo.working_directory_path()); | |
26 | ui.write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?; |
|
28 | ui.write_stdout(&format_bytes!(b"{}\n", bytes.as_slice()))?; | |
27 | Ok(()) |
|
29 | Ok(()) | |
28 | } |
|
30 | } |
@@ -1,115 +1,115 b'' | |||||
1 | use crate::ui::utf8_to_local; |
|
1 | use crate::ui::utf8_to_local; | |
2 | use crate::ui::UiError; |
|
2 | use crate::ui::UiError; | |
3 | use format_bytes::format_bytes; |
|
3 | use format_bytes::format_bytes; | |
4 | use hg::config::{ConfigError, ConfigParseError}; |
|
4 | use hg::config::{ConfigError, ConfigParseError}; | |
5 | use hg::errors::HgError; |
|
5 | use hg::errors::HgError; | |
6 | use hg::repo::RepoError; |
|
6 | use hg::repo::RepoError; | |
7 | use hg::revlog::revlog::RevlogError; |
|
7 | use hg::revlog::revlog::RevlogError; | |
8 | use hg::utils::files::get_bytes_from_path; |
|
8 | use hg::utils::files::get_bytes_from_path; | |
9 | use std::convert::From; |
|
9 | use std::convert::From; | |
10 |
|
10 | |||
11 | /// The kind of command error |
|
11 | /// The kind of command error | |
12 | #[derive(Debug)] |
|
12 | #[derive(Debug)] | |
13 | pub enum CommandError { |
|
13 | pub enum CommandError { | |
14 | /// Exit with an error message and "standard" failure exit code. |
|
14 | /// Exit with an error message and "standard" failure exit code. | |
15 | Abort { message: Vec<u8> }, |
|
15 | Abort { message: Vec<u8> }, | |
16 |
|
16 | |||
17 | /// A mercurial capability as not been implemented. |
|
17 | /// A mercurial capability as not been implemented. | |
18 | /// |
|
18 | /// | |
19 | /// There is no error message printed in this case. |
|
19 | /// There is no error message printed in this case. | |
20 | /// Instead, we exit with a specic status code and a wrapper script may |
|
20 | /// Instead, we exit with a specic status code and a wrapper script may | |
21 | /// fallback to Python-based Mercurial. |
|
21 | /// fallback to Python-based Mercurial. | |
22 | Unimplemented, |
|
22 | Unimplemented, | |
23 | } |
|
23 | } | |
24 |
|
24 | |||
25 | impl CommandError { |
|
25 | impl CommandError { | |
26 | pub fn abort(message: impl AsRef<str>) -> Self { |
|
26 | pub fn abort(message: impl AsRef<str>) -> Self { | |
27 | CommandError::Abort { |
|
27 | CommandError::Abort { | |
28 | // TODO: bytes-based (instead of Unicode-based) formatting |
|
28 | // TODO: bytes-based (instead of Unicode-based) formatting | |
29 | // of error messages to handle non-UTF-8 filenames etc: |
|
29 | // of error messages to handle non-UTF-8 filenames etc: | |
30 | // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output |
|
30 | // https://www.mercurial-scm.org/wiki/EncodingStrategy#Mixing_output | |
31 | message: utf8_to_local(message.as_ref()).into(), |
|
31 | message: utf8_to_local(message.as_ref()).into(), | |
32 | } |
|
32 | } | |
33 | } |
|
33 | } | |
34 | } |
|
34 | } | |
35 |
|
35 | |||
36 | impl From<HgError> for CommandError { |
|
36 | impl From<HgError> for CommandError { | |
37 | fn from(error: HgError) -> Self { |
|
37 | fn from(error: HgError) -> Self { | |
38 | match error { |
|
38 | match error { | |
39 | HgError::UnsupportedFeature(_) => CommandError::Unimplemented, |
|
39 | HgError::UnsupportedFeature(_) => CommandError::Unimplemented, | |
40 | _ => CommandError::abort(error.to_string()), |
|
40 | _ => CommandError::abort(error.to_string()), | |
41 | } |
|
41 | } | |
42 | } |
|
42 | } | |
43 | } |
|
43 | } | |
44 |
|
44 | |||
45 | impl From<UiError> for CommandError { |
|
45 | impl From<UiError> for CommandError { | |
46 | fn from(_error: UiError) -> Self { |
|
46 | fn from(_error: UiError) -> Self { | |
47 | // If we already failed writing to stdout or stderr, |
|
47 | // If we already failed writing to stdout or stderr, | |
48 | // writing an error message to stderr about it would be likely to fail |
|
48 | // writing an error message to stderr about it would be likely to fail | |
49 | // too. |
|
49 | // too. | |
50 | CommandError::abort("") |
|
50 | CommandError::abort("") | |
51 | } |
|
51 | } | |
52 | } |
|
52 | } | |
53 |
|
53 | |||
54 | impl From<RepoError> for CommandError { |
|
54 | impl From<RepoError> for CommandError { | |
55 | fn from(error: RepoError) -> Self { |
|
55 | fn from(error: RepoError) -> Self { | |
56 | match error { |
|
56 | match error { | |
57 |
RepoError::NotFound { |
|
57 | RepoError::NotFound { at } => CommandError::Abort { | |
58 | message: format_bytes!( |
|
58 | message: format_bytes!( | |
59 | b"no repository found in '{}' (.hg not found)!", |
|
59 | b"no repository found in '{}' (.hg not found)!", | |
60 |
get_bytes_from_path( |
|
60 | get_bytes_from_path(at) | |
61 | ), |
|
61 | ), | |
62 | }, |
|
62 | }, | |
63 | RepoError::ConfigParseError(error) => error.into(), |
|
63 | RepoError::ConfigParseError(error) => error.into(), | |
64 | RepoError::Other(error) => error.into(), |
|
64 | RepoError::Other(error) => error.into(), | |
65 | } |
|
65 | } | |
66 | } |
|
66 | } | |
67 | } |
|
67 | } | |
68 |
|
68 | |||
69 | impl From<ConfigError> for CommandError { |
|
69 | impl From<ConfigError> for CommandError { | |
70 | fn from(error: ConfigError) -> Self { |
|
70 | fn from(error: ConfigError) -> Self { | |
71 | match error { |
|
71 | match error { | |
72 | ConfigError::Parse(error) => error.into(), |
|
72 | ConfigError::Parse(error) => error.into(), | |
73 | ConfigError::Other(error) => error.into(), |
|
73 | ConfigError::Other(error) => error.into(), | |
74 | } |
|
74 | } | |
75 | } |
|
75 | } | |
76 | } |
|
76 | } | |
77 |
|
77 | |||
78 | impl From<ConfigParseError> for CommandError { |
|
78 | impl From<ConfigParseError> for CommandError { | |
79 | fn from(error: ConfigParseError) -> Self { |
|
79 | fn from(error: ConfigParseError) -> Self { | |
80 | let ConfigParseError { |
|
80 | let ConfigParseError { | |
81 | origin, |
|
81 | origin, | |
82 | line, |
|
82 | line, | |
83 | bytes, |
|
83 | bytes, | |
84 | } = error; |
|
84 | } = error; | |
85 | let line_message = if let Some(line_number) = line { |
|
85 | let line_message = if let Some(line_number) = line { | |
86 | format_bytes!(b" at line {}", line_number.to_string().into_bytes()) |
|
86 | format_bytes!(b" at line {}", line_number.to_string().into_bytes()) | |
87 | } else { |
|
87 | } else { | |
88 | Vec::new() |
|
88 | Vec::new() | |
89 | }; |
|
89 | }; | |
90 | CommandError::Abort { |
|
90 | CommandError::Abort { | |
91 | message: format_bytes!( |
|
91 | message: format_bytes!( | |
92 | b"config parse error in {}{}: '{}'", |
|
92 | b"config parse error in {}{}: '{}'", | |
93 | origin, |
|
93 | origin, | |
94 | line_message, |
|
94 | line_message, | |
95 | bytes |
|
95 | bytes | |
96 | ), |
|
96 | ), | |
97 | } |
|
97 | } | |
98 | } |
|
98 | } | |
99 | } |
|
99 | } | |
100 |
|
100 | |||
101 | impl From<(RevlogError, &str)> for CommandError { |
|
101 | impl From<(RevlogError, &str)> for CommandError { | |
102 | fn from((err, rev): (RevlogError, &str)) -> CommandError { |
|
102 | fn from((err, rev): (RevlogError, &str)) -> CommandError { | |
103 | match err { |
|
103 | match err { | |
104 | RevlogError::InvalidRevision => CommandError::abort(format!( |
|
104 | RevlogError::InvalidRevision => CommandError::abort(format!( | |
105 | "invalid revision identifier {}", |
|
105 | "invalid revision identifier {}", | |
106 | rev |
|
106 | rev | |
107 | )), |
|
107 | )), | |
108 | RevlogError::AmbiguousPrefix => CommandError::abort(format!( |
|
108 | RevlogError::AmbiguousPrefix => CommandError::abort(format!( | |
109 | "ambiguous revision identifier {}", |
|
109 | "ambiguous revision identifier {}", | |
110 | rev |
|
110 | rev | |
111 | )), |
|
111 | )), | |
112 | RevlogError::Other(error) => error.into(), |
|
112 | RevlogError::Other(error) => error.into(), | |
113 | } |
|
113 | } | |
114 | } |
|
114 | } | |
115 | } |
|
115 | } |
@@ -1,94 +1,116 b'' | |||||
1 | extern crate log; |
|
1 | extern crate log; | |
2 | use clap::App; |
|
2 | use clap::App; | |
3 | use clap::AppSettings; |
|
3 | use clap::AppSettings; | |
|
4 | use clap::Arg; | |||
4 | use clap::ArgMatches; |
|
5 | use clap::ArgMatches; | |
5 | use format_bytes::format_bytes; |
|
6 | use format_bytes::format_bytes; | |
|
7 | use std::path::Path; | |||
6 |
|
8 | |||
7 | mod error; |
|
9 | mod error; | |
8 | mod exitcode; |
|
10 | mod exitcode; | |
9 | mod ui; |
|
11 | mod ui; | |
10 | use error::CommandError; |
|
12 | use error::CommandError; | |
11 |
|
13 | |||
|
14 | fn add_global_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { | |||
|
15 | app.arg( | |||
|
16 | Arg::with_name("repository") | |||
|
17 | .help("repository root directory") | |||
|
18 | .short("-R") | |||
|
19 | .long("--repository") | |||
|
20 | .value_name("REPO") | |||
|
21 | .takes_value(true), | |||
|
22 | ) | |||
|
23 | } | |||
|
24 | ||||
12 | fn main() { |
|
25 | fn main() { | |
13 | env_logger::init(); |
|
26 | env_logger::init(); | |
14 | let app = App::new("rhg") |
|
27 | let app = App::new("rhg") | |
15 | .setting(AppSettings::AllowInvalidUtf8) |
|
28 | .setting(AppSettings::AllowInvalidUtf8) | |
16 | .setting(AppSettings::SubcommandRequired) |
|
29 | .setting(AppSettings::SubcommandRequired) | |
17 | .setting(AppSettings::VersionlessSubcommands) |
|
30 | .setting(AppSettings::VersionlessSubcommands) | |
18 | .version("0.0.1"); |
|
31 | .version("0.0.1"); | |
|
32 | let app = add_global_args(app); | |||
19 | let app = add_subcommand_args(app); |
|
33 | let app = add_subcommand_args(app); | |
20 |
|
34 | |||
21 | let ui = ui::Ui::new(); |
|
35 | let ui = ui::Ui::new(); | |
22 |
|
36 | |||
23 | let matches = app.clone().get_matches_safe().unwrap_or_else(|err| { |
|
37 | let matches = app.clone().get_matches_safe().unwrap_or_else(|err| { | |
24 | let _ = ui.writeln_stderr_str(&err.message); |
|
38 | let _ = ui.writeln_stderr_str(&err.message); | |
25 | std::process::exit(exitcode::UNIMPLEMENTED) |
|
39 | std::process::exit(exitcode::UNIMPLEMENTED) | |
26 | }); |
|
40 | }); | |
|
41 | ||||
27 | let (subcommand_name, subcommand_matches) = matches.subcommand(); |
|
42 | let (subcommand_name, subcommand_matches) = matches.subcommand(); | |
28 | let run = subcommand_run_fn(subcommand_name) |
|
43 | let run = subcommand_run_fn(subcommand_name) | |
29 | .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired"); |
|
44 | .expect("unknown subcommand name from clap despite AppSettings::SubcommandRequired"); | |
30 | let args = subcommand_matches |
|
45 | let args = subcommand_matches | |
31 | .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired"); |
|
46 | .expect("no subcommand arguments from clap despite AppSettings::SubcommandRequired"); | |
32 |
|
47 | |||
|
48 | // Global arguments can be in either based on e.g. `hg -R ./foo log` v.s. | |||
|
49 | // `hg log -R ./foo` | |||
|
50 | let global_arg = | |||
|
51 | |name| args.value_of_os(name).or_else(|| matches.value_of_os(name)); | |||
|
52 | ||||
|
53 | let repo_path = global_arg("repository").map(Path::new); | |||
33 | let result = (|| -> Result<(), CommandError> { |
|
54 | let result = (|| -> Result<(), CommandError> { | |
34 | let config = hg::config::Config::load()?; |
|
55 | let config = hg::config::Config::load()?; | |
35 | run(&ui, &config, args) |
|
56 | run(&ui, &config, repo_path, args) | |
36 | })(); |
|
57 | })(); | |
37 |
|
58 | |||
38 | let exit_code = match result { |
|
59 | let exit_code = match result { | |
39 | Ok(_) => exitcode::OK, |
|
60 | Ok(_) => exitcode::OK, | |
40 |
|
61 | |||
41 | // Exit with a specific code and no error message to let a potential |
|
62 | // Exit with a specific code and no error message to let a potential | |
42 | // wrapper script fallback to Python-based Mercurial. |
|
63 | // wrapper script fallback to Python-based Mercurial. | |
43 | Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED, |
|
64 | Err(CommandError::Unimplemented) => exitcode::UNIMPLEMENTED, | |
44 |
|
65 | |||
45 | Err(CommandError::Abort { message }) => { |
|
66 | Err(CommandError::Abort { message }) => { | |
46 | if !message.is_empty() { |
|
67 | if !message.is_empty() { | |
47 | // Ignore errors when writing to stderr, weβre already exiting |
|
68 | // Ignore errors when writing to stderr, weβre already exiting | |
48 | // with failure code so thereβs not much more we can do. |
|
69 | // with failure code so thereβs not much more we can do. | |
49 | let _ = |
|
70 | let _ = | |
50 | ui.write_stderr(&format_bytes!(b"abort: {}\n", message)); |
|
71 | ui.write_stderr(&format_bytes!(b"abort: {}\n", message)); | |
51 | } |
|
72 | } | |
52 | exitcode::ABORT |
|
73 | exitcode::ABORT | |
53 | } |
|
74 | } | |
54 | }; |
|
75 | }; | |
55 | std::process::exit(exit_code) |
|
76 | std::process::exit(exit_code) | |
56 | } |
|
77 | } | |
57 |
|
78 | |||
58 | macro_rules! subcommands { |
|
79 | macro_rules! subcommands { | |
59 | ($( $command: ident )+) => { |
|
80 | ($( $command: ident )+) => { | |
60 | mod commands { |
|
81 | mod commands { | |
61 | $( |
|
82 | $( | |
62 | pub mod $command; |
|
83 | pub mod $command; | |
63 | )+ |
|
84 | )+ | |
64 | } |
|
85 | } | |
65 |
|
86 | |||
66 | fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { |
|
87 | fn add_subcommand_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { | |
67 | app |
|
88 | app | |
68 | $( |
|
89 | $( | |
69 | .subcommand(commands::$command::args()) |
|
90 | .subcommand(add_global_args(commands::$command::args())) | |
70 | )+ |
|
91 | )+ | |
71 | } |
|
92 | } | |
72 |
|
93 | |||
73 | fn subcommand_run_fn(name: &str) -> Option<fn( |
|
94 | fn subcommand_run_fn(name: &str) -> Option<fn( | |
74 | &ui::Ui, |
|
95 | &ui::Ui, | |
75 | &hg::config::Config, |
|
96 | &hg::config::Config, | |
|
97 | Option<&Path>, | |||
76 | &ArgMatches, |
|
98 | &ArgMatches, | |
77 | ) -> Result<(), CommandError>> { |
|
99 | ) -> Result<(), CommandError>> { | |
78 | match name { |
|
100 | match name { | |
79 | $( |
|
101 | $( | |
80 | stringify!($command) => Some(commands::$command::run), |
|
102 | stringify!($command) => Some(commands::$command::run), | |
81 | )+ |
|
103 | )+ | |
82 | _ => None, |
|
104 | _ => None, | |
83 | } |
|
105 | } | |
84 | } |
|
106 | } | |
85 | }; |
|
107 | }; | |
86 | } |
|
108 | } | |
87 |
|
109 | |||
88 | subcommands! { |
|
110 | subcommands! { | |
89 | cat |
|
111 | cat | |
90 | debugdata |
|
112 | debugdata | |
91 | debugrequirements |
|
113 | debugrequirements | |
92 | files |
|
114 | files | |
93 | root |
|
115 | root | |
94 | } |
|
116 | } |
@@ -1,262 +1,257 b'' | |||||
1 | #require rust |
|
1 | #require rust | |
2 |
|
2 | |||
3 | Define an rhg function that will only run if rhg exists |
|
3 | Define an rhg function that will only run if rhg exists | |
4 | $ rhg() { |
|
4 | $ rhg() { | |
5 | > if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then |
|
5 | > if [ -f "$RUNTESTDIR/../rust/target/release/rhg" ]; then | |
6 | > "$RUNTESTDIR/../rust/target/release/rhg" "$@" |
|
6 | > "$RUNTESTDIR/../rust/target/release/rhg" "$@" | |
7 | > else |
|
7 | > else | |
8 | > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg." |
|
8 | > echo "skipped: Cannot find rhg. Try to run cargo build in rust/rhg." | |
9 | > exit 80 |
|
9 | > exit 80 | |
10 | > fi |
|
10 | > fi | |
11 | > } |
|
11 | > } | |
12 |
|
12 | |||
13 | Unimplemented command |
|
13 | Unimplemented command | |
14 | $ rhg unimplemented-command |
|
14 | $ rhg unimplemented-command | |
15 | error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context |
|
15 | error: Found argument 'unimplemented-command' which wasn't expected, or isn't valid in this context | |
16 |
|
16 | |||
17 | USAGE: |
|
17 | USAGE: | |
18 | rhg <SUBCOMMAND> |
|
18 | rhg [OPTIONS] <SUBCOMMAND> | |
19 |
|
19 | |||
20 | For more information try --help |
|
20 | For more information try --help | |
21 | [252] |
|
21 | [252] | |
22 |
|
22 | |||
23 | Finding root |
|
23 | Finding root | |
24 | $ rhg root |
|
24 | $ rhg root | |
25 | abort: no repository found in '$TESTTMP' (.hg not found)! |
|
25 | abort: no repository found in '$TESTTMP' (.hg not found)! | |
26 | [255] |
|
26 | [255] | |
27 |
|
27 | |||
28 | $ hg init repository |
|
28 | $ hg init repository | |
29 | $ cd repository |
|
29 | $ cd repository | |
30 | $ rhg root |
|
30 | $ rhg root | |
31 | $TESTTMP/repository |
|
31 | $TESTTMP/repository | |
32 |
|
32 | |||
33 | Unwritable file descriptor |
|
33 | Unwritable file descriptor | |
34 | $ rhg root > /dev/full |
|
34 | $ rhg root > /dev/full | |
35 | abort: No space left on device (os error 28) |
|
35 | abort: No space left on device (os error 28) | |
36 | [255] |
|
36 | [255] | |
37 |
|
37 | |||
38 | Deleted repository |
|
38 | Deleted repository | |
39 | $ rm -rf `pwd` |
|
39 | $ rm -rf `pwd` | |
40 | $ rhg root |
|
40 | $ rhg root | |
41 | abort: $ENOENT$: current directory |
|
41 | abort: $ENOENT$: current directory | |
42 | [255] |
|
42 | [255] | |
43 |
|
43 | |||
44 | Listing tracked files |
|
44 | Listing tracked files | |
45 | $ cd $TESTTMP |
|
45 | $ cd $TESTTMP | |
46 | $ hg init repository |
|
46 | $ hg init repository | |
47 | $ cd repository |
|
47 | $ cd repository | |
48 | $ for i in 1 2 3; do |
|
48 | $ for i in 1 2 3; do | |
49 | > echo $i >> file$i |
|
49 | > echo $i >> file$i | |
50 | > hg add file$i |
|
50 | > hg add file$i | |
51 | > done |
|
51 | > done | |
52 | > hg commit -m "commit $i" -q |
|
52 | > hg commit -m "commit $i" -q | |
53 |
|
53 | |||
54 | Listing tracked files from root |
|
54 | Listing tracked files from root | |
55 | $ rhg files |
|
55 | $ rhg files | |
56 | file1 |
|
56 | file1 | |
57 | file2 |
|
57 | file2 | |
58 | file3 |
|
58 | file3 | |
59 |
|
59 | |||
60 | Listing tracked files from subdirectory |
|
60 | Listing tracked files from subdirectory | |
61 | $ mkdir -p path/to/directory |
|
61 | $ mkdir -p path/to/directory | |
62 | $ cd path/to/directory |
|
62 | $ cd path/to/directory | |
63 | $ rhg files |
|
63 | $ rhg files | |
64 | ../../../file1 |
|
64 | ../../../file1 | |
65 | ../../../file2 |
|
65 | ../../../file2 | |
66 | ../../../file3 |
|
66 | ../../../file3 | |
67 |
|
67 | |||
68 | Listing tracked files through broken pipe |
|
68 | Listing tracked files through broken pipe | |
69 | $ rhg files | head -n 1 |
|
69 | $ rhg files | head -n 1 | |
70 | ../../../file1 |
|
70 | ../../../file1 | |
71 |
|
71 | |||
72 | Debuging data in inline index |
|
72 | Debuging data in inline index | |
73 | $ cd $TESTTMP |
|
73 | $ cd $TESTTMP | |
74 | $ rm -rf repository |
|
74 | $ rm -rf repository | |
75 | $ hg init repository |
|
75 | $ hg init repository | |
76 | $ cd repository |
|
76 | $ cd repository | |
77 | $ for i in 1 2 3 4 5 6; do |
|
77 | $ for i in 1 2 3 4 5 6; do | |
78 | > echo $i >> file-$i |
|
78 | > echo $i >> file-$i | |
79 | > hg add file-$i |
|
79 | > hg add file-$i | |
80 | > hg commit -m "Commit $i" -q |
|
80 | > hg commit -m "Commit $i" -q | |
81 | > done |
|
81 | > done | |
82 | $ rhg debugdata -c 2 |
|
82 | $ rhg debugdata -c 2 | |
83 | 8d0267cb034247ebfa5ee58ce59e22e57a492297 |
|
83 | 8d0267cb034247ebfa5ee58ce59e22e57a492297 | |
84 | test |
|
84 | test | |
85 | 0 0 |
|
85 | 0 0 | |
86 | file-3 |
|
86 | file-3 | |
87 |
|
87 | |||
88 | Commit 3 (no-eol) |
|
88 | Commit 3 (no-eol) | |
89 | $ rhg debugdata -m 2 |
|
89 | $ rhg debugdata -m 2 | |
90 | file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) |
|
90 | file-1\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc) | |
91 | file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc) |
|
91 | file-2\x005d9299349fc01ddd25d0070d149b124d8f10411e (esc) | |
92 | file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc) |
|
92 | file-3\x002661d26c649684b482d10f91960cc3db683c38b4 (esc) | |
93 |
|
93 | |||
94 | Debuging with full node id |
|
94 | Debuging with full node id | |
95 | $ rhg debugdata -c `hg log -r 0 -T '{node}'` |
|
95 | $ rhg debugdata -c `hg log -r 0 -T '{node}'` | |
96 | d1d1c679d3053e8926061b6f45ca52009f011e3f |
|
96 | d1d1c679d3053e8926061b6f45ca52009f011e3f | |
97 | test |
|
97 | test | |
98 | 0 0 |
|
98 | 0 0 | |
99 | file-1 |
|
99 | file-1 | |
100 |
|
100 | |||
101 | Commit 1 (no-eol) |
|
101 | Commit 1 (no-eol) | |
102 |
|
102 | |||
103 | Specifying revisions by changeset ID |
|
103 | Specifying revisions by changeset ID | |
104 | $ hg log -T '{node}\n' |
|
104 | $ hg log -T '{node}\n' | |
105 | c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b |
|
105 | c6ad58c44207b6ff8a4fbbca7045a5edaa7e908b | |
106 | d654274993d0149eecc3cc03214f598320211900 |
|
106 | d654274993d0149eecc3cc03214f598320211900 | |
107 | f646af7e96481d3a5470b695cf30ad8e3ab6c575 |
|
107 | f646af7e96481d3a5470b695cf30ad8e3ab6c575 | |
108 | cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7 |
|
108 | cf8b83f14ead62b374b6e91a0e9303b85dfd9ed7 | |
109 | 91c6f6e73e39318534dc415ea4e8a09c99cd74d6 |
|
109 | 91c6f6e73e39318534dc415ea4e8a09c99cd74d6 | |
110 | 6ae9681c6d30389694d8701faf24b583cf3ccafe |
|
110 | 6ae9681c6d30389694d8701faf24b583cf3ccafe | |
111 | $ rhg files -r cf8b83 |
|
111 | $ rhg files -r cf8b83 | |
112 | file-1 |
|
112 | file-1 | |
113 | file-2 |
|
113 | file-2 | |
114 | file-3 |
|
114 | file-3 | |
115 | $ rhg cat -r cf8b83 file-2 |
|
115 | $ rhg cat -r cf8b83 file-2 | |
116 | 2 |
|
116 | 2 | |
117 | $ rhg cat -r c file-2 |
|
117 | $ rhg cat -r c file-2 | |
118 | abort: ambiguous revision identifier c |
|
118 | abort: ambiguous revision identifier c | |
119 | [255] |
|
119 | [255] | |
120 | $ rhg cat -r d file-2 |
|
120 | $ rhg cat -r d file-2 | |
121 | 2 |
|
121 | 2 | |
122 |
|
122 | |||
123 | Cat files |
|
123 | Cat files | |
124 | $ cd $TESTTMP |
|
124 | $ cd $TESTTMP | |
125 | $ rm -rf repository |
|
125 | $ rm -rf repository | |
126 | $ hg init repository |
|
126 | $ hg init repository | |
127 | $ cd repository |
|
127 | $ cd repository | |
128 | $ echo "original content" > original |
|
128 | $ echo "original content" > original | |
129 | $ hg add original |
|
129 | $ hg add original | |
130 | $ hg commit -m "add original" original |
|
130 | $ hg commit -m "add original" original | |
131 | $ rhg cat -r 0 original |
|
131 | $ rhg cat -r 0 original | |
132 | original content |
|
132 | original content | |
133 | Cat copied file should not display copy metadata |
|
133 | Cat copied file should not display copy metadata | |
134 | $ hg copy original copy_of_original |
|
134 | $ hg copy original copy_of_original | |
135 | $ hg commit -m "add copy of original" |
|
135 | $ hg commit -m "add copy of original" | |
136 | $ rhg cat -r 1 copy_of_original |
|
136 | $ rhg cat -r 1 copy_of_original | |
137 | original content |
|
137 | original content | |
138 |
|
138 | |||
139 | Requirements |
|
139 | Requirements | |
140 | $ rhg debugrequirements |
|
140 | $ rhg debugrequirements | |
141 | dotencode |
|
141 | dotencode | |
142 | fncache |
|
142 | fncache | |
143 | generaldelta |
|
143 | generaldelta | |
144 | revlogv1 |
|
144 | revlogv1 | |
145 | sparserevlog |
|
145 | sparserevlog | |
146 | store |
|
146 | store | |
147 |
|
147 | |||
148 | $ echo indoor-pool >> .hg/requires |
|
148 | $ echo indoor-pool >> .hg/requires | |
149 | $ rhg files |
|
149 | $ rhg files | |
150 | [252] |
|
150 | [252] | |
151 |
|
151 | |||
152 | $ rhg cat -r 1 copy_of_original |
|
152 | $ rhg cat -r 1 copy_of_original | |
153 | [252] |
|
153 | [252] | |
154 |
|
154 | |||
155 | $ rhg debugrequirements |
|
155 | $ rhg debugrequirements | |
156 | [252] |
|
156 | [252] | |
157 |
|
157 | |||
158 | $ echo -e '\xFF' >> .hg/requires |
|
158 | $ echo -e '\xFF' >> .hg/requires | |
159 | $ rhg debugrequirements |
|
159 | $ rhg debugrequirements | |
160 | abort: corrupted repository: parse error in 'requires' file |
|
160 | abort: corrupted repository: parse error in 'requires' file | |
161 | [255] |
|
161 | [255] | |
162 |
|
162 | |||
163 | Persistent nodemap |
|
163 | Persistent nodemap | |
164 | $ cd $TESTTMP |
|
164 | $ cd $TESTTMP | |
165 | $ rm -rf repository |
|
165 | $ rm -rf repository | |
166 | $ hg init repository |
|
166 | $ hg init repository | |
167 | $ cd repository |
|
167 | $ cd repository | |
168 | $ rhg debugrequirements | grep nodemap |
|
168 | $ rhg debugrequirements | grep nodemap | |
169 | [1] |
|
169 | [1] | |
170 | $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn" |
|
170 | $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn" | |
171 | $ hg id -r tip |
|
171 | $ hg id -r tip | |
172 | c3ae8dec9fad tip |
|
172 | c3ae8dec9fad tip | |
173 | $ ls .hg/store/00changelog* |
|
173 | $ ls .hg/store/00changelog* | |
174 | .hg/store/00changelog.d |
|
174 | .hg/store/00changelog.d | |
175 | .hg/store/00changelog.i |
|
175 | .hg/store/00changelog.i | |
176 | $ rhg files -r c3ae8dec9fad |
|
176 | $ rhg files -r c3ae8dec9fad | |
177 | of |
|
177 | of | |
178 |
|
178 | |||
179 | $ cd $TESTTMP |
|
179 | $ cd $TESTTMP | |
180 | $ rm -rf repository |
|
180 | $ rm -rf repository | |
181 | $ hg --config format.use-persistent-nodemap=True init repository |
|
181 | $ hg --config format.use-persistent-nodemap=True init repository | |
182 | $ cd repository |
|
182 | $ cd repository | |
183 | $ rhg debugrequirements | grep nodemap |
|
183 | $ rhg debugrequirements | grep nodemap | |
184 | persistent-nodemap |
|
184 | persistent-nodemap | |
185 | $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn" |
|
185 | $ hg debugbuilddag .+5000 --overwritten-file --config "storage.revlog.nodemap.mode=warn" | |
186 | $ hg id -r tip |
|
186 | $ hg id -r tip | |
187 | c3ae8dec9fad tip |
|
187 | c3ae8dec9fad tip | |
188 | $ ls .hg/store/00changelog* |
|
188 | $ ls .hg/store/00changelog* | |
189 | .hg/store/00changelog-*.nd (glob) |
|
189 | .hg/store/00changelog-*.nd (glob) | |
190 | .hg/store/00changelog.d |
|
190 | .hg/store/00changelog.d | |
191 | .hg/store/00changelog.i |
|
191 | .hg/store/00changelog.i | |
192 | .hg/store/00changelog.n |
|
192 | .hg/store/00changelog.n | |
193 |
|
193 | |||
194 | Specifying revisions by changeset ID |
|
194 | Specifying revisions by changeset ID | |
195 | $ rhg files -r c3ae8dec9fad |
|
195 | $ rhg files -r c3ae8dec9fad | |
196 | of |
|
196 | of | |
197 | $ rhg cat -r c3ae8dec9fad of |
|
197 | $ rhg cat -r c3ae8dec9fad of | |
198 | r5000 |
|
198 | r5000 | |
199 |
|
199 | |||
200 | Crate a shared repository |
|
200 | Crate a shared repository | |
201 |
|
201 | |||
202 | $ echo "[extensions]" >> $HGRCPATH |
|
202 | $ echo "[extensions]" >> $HGRCPATH | |
203 | $ echo "share = " >> $HGRCPATH |
|
203 | $ echo "share = " >> $HGRCPATH | |
204 |
|
204 | |||
205 | $ cd $TESTTMP |
|
205 | $ cd $TESTTMP | |
206 | $ hg init repo1 |
|
206 | $ hg init repo1 | |
207 |
$ |
|
207 | $ echo a > repo1/a | |
208 | $ echo a > a |
|
208 | $ hg -R repo1 commit -A -m'init' | |
209 | $ hg commit -A -m'init' |
|
|||
210 | adding a |
|
209 | adding a | |
211 |
|
210 | |||
212 | $ cd .. |
|
|||
213 | $ hg share repo1 repo2 |
|
211 | $ hg share repo1 repo2 | |
214 | updating working directory |
|
212 | updating working directory | |
215 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
213 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
216 |
|
214 | |||
217 | And check that basic rhg commands work with sharing |
|
215 | And check that basic rhg commands work with sharing | |
218 |
|
216 | |||
219 | $ cd repo2 |
|
217 | $ rhg files -R repo2 | |
220 | $ rhg files |
|
218 | repo2/a | |
221 | a |
|
219 | $ rhg -R repo2 cat -r 0 repo2/a | |
222 | $ rhg cat -r 0 a |
|
|||
223 | a |
|
220 | a | |
224 |
|
221 | |||
225 | Same with relative sharing |
|
222 | Same with relative sharing | |
226 |
|
223 | |||
227 | $ cd .. |
|
|||
228 | $ hg share repo2 repo3 --relative |
|
224 | $ hg share repo2 repo3 --relative | |
229 | updating working directory |
|
225 | updating working directory | |
230 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
226 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
231 |
|
227 | |||
232 | $ cd repo3 |
|
228 | $ rhg files -R repo3 | |
233 | $ rhg files |
|
229 | repo3/a | |
234 | a |
|
230 | $ rhg -R repo3 cat -r 0 repo3/a | |
235 | $ rhg cat -r 0 a |
|
|||
236 | a |
|
231 | a | |
237 |
|
232 | |||
238 | Same with share-safe |
|
233 | Same with share-safe | |
239 |
|
234 | |||
240 | $ echo "[format]" >> $HGRCPATH |
|
235 | $ echo "[format]" >> $HGRCPATH | |
241 | $ echo "use-share-safe = True" >> $HGRCPATH |
|
236 | $ echo "use-share-safe = True" >> $HGRCPATH | |
242 |
|
237 | |||
243 | $ cd $TESTTMP |
|
238 | $ cd $TESTTMP | |
244 | $ hg init repo4 |
|
239 | $ hg init repo4 | |
245 | $ cd repo4 |
|
240 | $ cd repo4 | |
246 | $ echo a > a |
|
241 | $ echo a > a | |
247 | $ hg commit -A -m'init' |
|
242 | $ hg commit -A -m'init' | |
248 | adding a |
|
243 | adding a | |
249 |
|
244 | |||
250 | $ cd .. |
|
245 | $ cd .. | |
251 | $ hg share repo4 repo5 |
|
246 | $ hg share repo4 repo5 | |
252 | updating working directory |
|
247 | updating working directory | |
253 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved |
|
248 | 1 files updated, 0 files merged, 0 files removed, 0 files unresolved | |
254 |
|
249 | |||
255 | And check that basic rhg commands work with sharing |
|
250 | And check that basic rhg commands work with sharing | |
256 |
|
251 | |||
257 | $ cd repo5 |
|
252 | $ cd repo5 | |
258 | $ rhg files |
|
253 | $ rhg files | |
259 | a |
|
254 | a | |
260 | $ rhg cat -r 0 a |
|
255 | $ rhg cat -r 0 a | |
261 | a |
|
256 | a | |
262 |
|
257 |
General Comments 0
You need to be logged in to leave comments.
Login now