##// END OF EJS Templates
rhg: fallback if tweakdefaults or statuscopies is enabled with status...
Pulkit Goyal -
r48985:64b8676f default
parent child Browse files
Show More
@@ -1,280 +1,292 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use clap::{Arg, SubCommand};
10 use clap::{Arg, SubCommand};
11 use hg;
11 use hg;
12 use hg::errors::HgError;
12 use hg::errors::HgError;
13 use hg::manifest::Manifest;
13 use hg::manifest::Manifest;
14 use hg::matchers::AlwaysMatcher;
14 use hg::matchers::AlwaysMatcher;
15 use hg::repo::Repo;
15 use hg::repo::Repo;
16 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
16 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
17 use hg::{HgPathCow, StatusOptions};
17 use hg::{HgPathCow, StatusOptions};
18 use log::{info, warn};
18 use log::{info, warn};
19
19
20 pub const HELP_TEXT: &str = "
20 pub const HELP_TEXT: &str = "
21 Show changed files in the working directory
21 Show changed files in the working directory
22
22
23 This is a pure Rust version of `hg status`.
23 This is a pure Rust version of `hg status`.
24
24
25 Some options might be missing, check the list below.
25 Some options might be missing, check the list below.
26 ";
26 ";
27
27
28 pub fn args() -> clap::App<'static, 'static> {
28 pub fn args() -> clap::App<'static, 'static> {
29 SubCommand::with_name("status")
29 SubCommand::with_name("status")
30 .alias("st")
30 .alias("st")
31 .about(HELP_TEXT)
31 .about(HELP_TEXT)
32 .arg(
32 .arg(
33 Arg::with_name("all")
33 Arg::with_name("all")
34 .help("show status of all files")
34 .help("show status of all files")
35 .short("-A")
35 .short("-A")
36 .long("--all"),
36 .long("--all"),
37 )
37 )
38 .arg(
38 .arg(
39 Arg::with_name("modified")
39 Arg::with_name("modified")
40 .help("show only modified files")
40 .help("show only modified files")
41 .short("-m")
41 .short("-m")
42 .long("--modified"),
42 .long("--modified"),
43 )
43 )
44 .arg(
44 .arg(
45 Arg::with_name("added")
45 Arg::with_name("added")
46 .help("show only added files")
46 .help("show only added files")
47 .short("-a")
47 .short("-a")
48 .long("--added"),
48 .long("--added"),
49 )
49 )
50 .arg(
50 .arg(
51 Arg::with_name("removed")
51 Arg::with_name("removed")
52 .help("show only removed files")
52 .help("show only removed files")
53 .short("-r")
53 .short("-r")
54 .long("--removed"),
54 .long("--removed"),
55 )
55 )
56 .arg(
56 .arg(
57 Arg::with_name("clean")
57 Arg::with_name("clean")
58 .help("show only clean files")
58 .help("show only clean files")
59 .short("-c")
59 .short("-c")
60 .long("--clean"),
60 .long("--clean"),
61 )
61 )
62 .arg(
62 .arg(
63 Arg::with_name("deleted")
63 Arg::with_name("deleted")
64 .help("show only deleted files")
64 .help("show only deleted files")
65 .short("-d")
65 .short("-d")
66 .long("--deleted"),
66 .long("--deleted"),
67 )
67 )
68 .arg(
68 .arg(
69 Arg::with_name("unknown")
69 Arg::with_name("unknown")
70 .help("show only unknown (not tracked) files")
70 .help("show only unknown (not tracked) files")
71 .short("-u")
71 .short("-u")
72 .long("--unknown"),
72 .long("--unknown"),
73 )
73 )
74 .arg(
74 .arg(
75 Arg::with_name("ignored")
75 Arg::with_name("ignored")
76 .help("show only ignored files")
76 .help("show only ignored files")
77 .short("-i")
77 .short("-i")
78 .long("--ignored"),
78 .long("--ignored"),
79 )
79 )
80 }
80 }
81
81
82 /// Pure data type allowing the caller to specify file states to display
82 /// Pure data type allowing the caller to specify file states to display
83 #[derive(Copy, Clone, Debug)]
83 #[derive(Copy, Clone, Debug)]
84 pub struct DisplayStates {
84 pub struct DisplayStates {
85 pub modified: bool,
85 pub modified: bool,
86 pub added: bool,
86 pub added: bool,
87 pub removed: bool,
87 pub removed: bool,
88 pub clean: bool,
88 pub clean: bool,
89 pub deleted: bool,
89 pub deleted: bool,
90 pub unknown: bool,
90 pub unknown: bool,
91 pub ignored: bool,
91 pub ignored: bool,
92 }
92 }
93
93
94 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
94 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
95 modified: true,
95 modified: true,
96 added: true,
96 added: true,
97 removed: true,
97 removed: true,
98 clean: false,
98 clean: false,
99 deleted: true,
99 deleted: true,
100 unknown: true,
100 unknown: true,
101 ignored: false,
101 ignored: false,
102 };
102 };
103
103
104 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
104 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
105 modified: true,
105 modified: true,
106 added: true,
106 added: true,
107 removed: true,
107 removed: true,
108 clean: true,
108 clean: true,
109 deleted: true,
109 deleted: true,
110 unknown: true,
110 unknown: true,
111 ignored: true,
111 ignored: true,
112 };
112 };
113
113
114 impl DisplayStates {
114 impl DisplayStates {
115 pub fn is_empty(&self) -> bool {
115 pub fn is_empty(&self) -> bool {
116 !(self.modified
116 !(self.modified
117 || self.added
117 || self.added
118 || self.removed
118 || self.removed
119 || self.clean
119 || self.clean
120 || self.deleted
120 || self.deleted
121 || self.unknown
121 || self.unknown
122 || self.ignored)
122 || self.ignored)
123 }
123 }
124 }
124 }
125
125
126 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
126 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
127 let status_enabled_default = false;
127 let status_enabled_default = false;
128 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
128 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
129 if !status_enabled.unwrap_or(status_enabled_default) {
129 if !status_enabled.unwrap_or(status_enabled_default) {
130 return Err(CommandError::unsupported(
130 return Err(CommandError::unsupported(
131 "status is experimental in rhg (enable it with 'rhg.status = true' \
131 "status is experimental in rhg (enable it with 'rhg.status = true' \
132 or enable fallback with 'rhg.on-unsupported = fallback')"
132 or enable fallback with 'rhg.on-unsupported = fallback')"
133 ));
133 ));
134 }
134 }
135
135
136 // TODO: lift these limitations
137 if invocation.config.get_bool(b"ui", b"tweakdefaults").ok() == Some(true) {
138 return Err(CommandError::unsupported(
139 "ui.tweakdefaults is not yet supported with rhg status",
140 ));
141 }
142 if invocation.config.get_bool(b"ui", b"statuscopies").ok() == Some(true) {
143 return Err(CommandError::unsupported(
144 "ui.statuscopies is not yet supported with rhg status",
145 ));
146 }
147
136 let ui = invocation.ui;
148 let ui = invocation.ui;
137 let args = invocation.subcommand_args;
149 let args = invocation.subcommand_args;
138 let display_states = if args.is_present("all") {
150 let display_states = if args.is_present("all") {
139 // TODO when implementing `--quiet`: it excludes clean files
151 // TODO when implementing `--quiet`: it excludes clean files
140 // from `--all`
152 // from `--all`
141 ALL_DISPLAY_STATES
153 ALL_DISPLAY_STATES
142 } else {
154 } else {
143 let requested = DisplayStates {
155 let requested = DisplayStates {
144 modified: args.is_present("modified"),
156 modified: args.is_present("modified"),
145 added: args.is_present("added"),
157 added: args.is_present("added"),
146 removed: args.is_present("removed"),
158 removed: args.is_present("removed"),
147 clean: args.is_present("clean"),
159 clean: args.is_present("clean"),
148 deleted: args.is_present("deleted"),
160 deleted: args.is_present("deleted"),
149 unknown: args.is_present("unknown"),
161 unknown: args.is_present("unknown"),
150 ignored: args.is_present("ignored"),
162 ignored: args.is_present("ignored"),
151 };
163 };
152 if requested.is_empty() {
164 if requested.is_empty() {
153 DEFAULT_DISPLAY_STATES
165 DEFAULT_DISPLAY_STATES
154 } else {
166 } else {
155 requested
167 requested
156 }
168 }
157 };
169 };
158
170
159 let repo = invocation.repo?;
171 let repo = invocation.repo?;
160 let mut dmap = repo.dirstate_map_mut()?;
172 let mut dmap = repo.dirstate_map_mut()?;
161
173
162 let options = StatusOptions {
174 let options = StatusOptions {
163 // TODO should be provided by the dirstate parsing and
175 // TODO should be provided by the dirstate parsing and
164 // hence be stored on dmap. Using a value that assumes we aren't
176 // hence be stored on dmap. Using a value that assumes we aren't
165 // below the time resolution granularity of the FS and the
177 // below the time resolution granularity of the FS and the
166 // dirstate.
178 // dirstate.
167 last_normal_time: 0,
179 last_normal_time: 0,
168 // we're currently supporting file systems with exec flags only
180 // we're currently supporting file systems with exec flags only
169 // anyway
181 // anyway
170 check_exec: true,
182 check_exec: true,
171 list_clean: display_states.clean,
183 list_clean: display_states.clean,
172 list_unknown: display_states.unknown,
184 list_unknown: display_states.unknown,
173 list_ignored: display_states.ignored,
185 list_ignored: display_states.ignored,
174 collect_traversed_dirs: false,
186 collect_traversed_dirs: false,
175 };
187 };
176 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
188 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
177 let (mut ds_status, pattern_warnings) = dmap.status(
189 let (mut ds_status, pattern_warnings) = dmap.status(
178 &AlwaysMatcher,
190 &AlwaysMatcher,
179 repo.working_directory_path().to_owned(),
191 repo.working_directory_path().to_owned(),
180 vec![ignore_file],
192 vec![ignore_file],
181 options,
193 options,
182 )?;
194 )?;
183 if !pattern_warnings.is_empty() {
195 if !pattern_warnings.is_empty() {
184 warn!("Pattern warnings: {:?}", &pattern_warnings);
196 warn!("Pattern warnings: {:?}", &pattern_warnings);
185 }
197 }
186
198
187 if !ds_status.bad.is_empty() {
199 if !ds_status.bad.is_empty() {
188 warn!("Bad matches {:?}", &(ds_status.bad))
200 warn!("Bad matches {:?}", &(ds_status.bad))
189 }
201 }
190 if !ds_status.unsure.is_empty() {
202 if !ds_status.unsure.is_empty() {
191 info!(
203 info!(
192 "Files to be rechecked by retrieval from filelog: {:?}",
204 "Files to be rechecked by retrieval from filelog: {:?}",
193 &ds_status.unsure
205 &ds_status.unsure
194 );
206 );
195 }
207 }
196 if !ds_status.unsure.is_empty()
208 if !ds_status.unsure.is_empty()
197 && (display_states.modified || display_states.clean)
209 && (display_states.modified || display_states.clean)
198 {
210 {
199 let p1 = repo.dirstate_parents()?.p1;
211 let p1 = repo.dirstate_parents()?.p1;
200 let manifest = repo.manifest_for_node(p1).map_err(|e| {
212 let manifest = repo.manifest_for_node(p1).map_err(|e| {
201 CommandError::from((e, &*format!("{:x}", p1.short())))
213 CommandError::from((e, &*format!("{:x}", p1.short())))
202 })?;
214 })?;
203 for to_check in ds_status.unsure {
215 for to_check in ds_status.unsure {
204 if cat_file_is_modified(repo, &manifest, &to_check)? {
216 if cat_file_is_modified(repo, &manifest, &to_check)? {
205 if display_states.modified {
217 if display_states.modified {
206 ds_status.modified.push(to_check);
218 ds_status.modified.push(to_check);
207 }
219 }
208 } else {
220 } else {
209 if display_states.clean {
221 if display_states.clean {
210 ds_status.clean.push(to_check);
222 ds_status.clean.push(to_check);
211 }
223 }
212 }
224 }
213 }
225 }
214 }
226 }
215 if display_states.modified {
227 if display_states.modified {
216 display_status_paths(ui, &mut ds_status.modified, b"M")?;
228 display_status_paths(ui, &mut ds_status.modified, b"M")?;
217 }
229 }
218 if display_states.added {
230 if display_states.added {
219 display_status_paths(ui, &mut ds_status.added, b"A")?;
231 display_status_paths(ui, &mut ds_status.added, b"A")?;
220 }
232 }
221 if display_states.removed {
233 if display_states.removed {
222 display_status_paths(ui, &mut ds_status.removed, b"R")?;
234 display_status_paths(ui, &mut ds_status.removed, b"R")?;
223 }
235 }
224 if display_states.deleted {
236 if display_states.deleted {
225 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
237 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
226 }
238 }
227 if display_states.unknown {
239 if display_states.unknown {
228 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
240 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
229 }
241 }
230 if display_states.ignored {
242 if display_states.ignored {
231 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
243 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
232 }
244 }
233 if display_states.clean {
245 if display_states.clean {
234 display_status_paths(ui, &mut ds_status.clean, b"C")?;
246 display_status_paths(ui, &mut ds_status.clean, b"C")?;
235 }
247 }
236 Ok(())
248 Ok(())
237 }
249 }
238
250
239 // Probably more elegant to use a Deref or Borrow trait rather than
251 // Probably more elegant to use a Deref or Borrow trait rather than
240 // harcode HgPathBuf, but probably not really useful at this point
252 // harcode HgPathBuf, but probably not really useful at this point
241 fn display_status_paths(
253 fn display_status_paths(
242 ui: &Ui,
254 ui: &Ui,
243 paths: &mut [HgPathCow],
255 paths: &mut [HgPathCow],
244 status_prefix: &[u8],
256 status_prefix: &[u8],
245 ) -> Result<(), CommandError> {
257 ) -> Result<(), CommandError> {
246 paths.sort_unstable();
258 paths.sort_unstable();
247 for path in paths {
259 for path in paths {
248 // Same TODO as in commands::root
260 // Same TODO as in commands::root
249 let bytes: &[u8] = path.as_bytes();
261 let bytes: &[u8] = path.as_bytes();
250 // TODO optim, probably lots of unneeded copies here, especially
262 // TODO optim, probably lots of unneeded copies here, especially
251 // if out stream is buffered
263 // if out stream is buffered
252 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
264 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
253 }
265 }
254 Ok(())
266 Ok(())
255 }
267 }
256
268
257 /// Check if a file is modified by comparing actual repo store and file system.
269 /// Check if a file is modified by comparing actual repo store and file system.
258 ///
270 ///
259 /// This meant to be used for those that the dirstate cannot resolve, due
271 /// This meant to be used for those that the dirstate cannot resolve, due
260 /// to time resolution limits.
272 /// to time resolution limits.
261 ///
273 ///
262 /// TODO: detect permission bits and similar metadata modifications
274 /// TODO: detect permission bits and similar metadata modifications
263 fn cat_file_is_modified(
275 fn cat_file_is_modified(
264 repo: &Repo,
276 repo: &Repo,
265 manifest: &Manifest,
277 manifest: &Manifest,
266 hg_path: &HgPath,
278 hg_path: &HgPath,
267 ) -> Result<bool, HgError> {
279 ) -> Result<bool, HgError> {
268 let file_node = manifest
280 let file_node = manifest
269 .find_file(hg_path)?
281 .find_file(hg_path)?
270 .expect("ambgious file not in p1");
282 .expect("ambgious file not in p1");
271 let filelog = repo.filelog(hg_path)?;
283 let filelog = repo.filelog(hg_path)?;
272 let filelog_entry = filelog.data_for_node(file_node).map_err(|_| {
284 let filelog_entry = filelog.data_for_node(file_node).map_err(|_| {
273 HgError::corrupted("filelog missing node from manifest")
285 HgError::corrupted("filelog missing node from manifest")
274 })?;
286 })?;
275 let contents_in_p1 = filelog_entry.data()?;
287 let contents_in_p1 = filelog_entry.data()?;
276
288
277 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
289 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
278 let fs_contents = repo.working_directory_vfs().read(fs_path)?;
290 let fs_contents = repo.working_directory_vfs().read(fs_path)?;
279 return Ok(contents_in_p1 == &*fs_contents);
291 return Ok(contents_in_p1 == &*fs_contents);
280 }
292 }
General Comments 0
You need to be logged in to leave comments. Login now