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