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