##// END OF EJS Templates
rhg: Config commands.status.terse is not supported...
Simon Sapin -
r49161:f9db8eeb default
parent child Browse files
Show More
@@ -1,315 +1,324 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, UiError};
10 10 use crate::utils::path_utils::relativize_paths;
11 11 use clap::{Arg, SubCommand};
12 12 use hg;
13 13 use hg::config::Config;
14 14 use hg::dirstate::TruncatedTimestamp;
15 15 use hg::errors::HgError;
16 16 use hg::manifest::Manifest;
17 17 use hg::matchers::AlwaysMatcher;
18 18 use hg::repo::Repo;
19 19 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
20 20 use hg::{HgPathCow, StatusOptions};
21 21 use log::{info, warn};
22 22 use std::borrow::Cow;
23 23
24 24 pub const HELP_TEXT: &str = "
25 25 Show changed files in the working directory
26 26
27 27 This is a pure Rust version of `hg status`.
28 28
29 29 Some options might be missing, check the list below.
30 30 ";
31 31
32 32 pub fn args() -> clap::App<'static, 'static> {
33 33 SubCommand::with_name("status")
34 34 .alias("st")
35 35 .about(HELP_TEXT)
36 36 .arg(
37 37 Arg::with_name("all")
38 38 .help("show status of all files")
39 39 .short("-A")
40 40 .long("--all"),
41 41 )
42 42 .arg(
43 43 Arg::with_name("modified")
44 44 .help("show only modified files")
45 45 .short("-m")
46 46 .long("--modified"),
47 47 )
48 48 .arg(
49 49 Arg::with_name("added")
50 50 .help("show only added files")
51 51 .short("-a")
52 52 .long("--added"),
53 53 )
54 54 .arg(
55 55 Arg::with_name("removed")
56 56 .help("show only removed files")
57 57 .short("-r")
58 58 .long("--removed"),
59 59 )
60 60 .arg(
61 61 Arg::with_name("clean")
62 62 .help("show only clean files")
63 63 .short("-c")
64 64 .long("--clean"),
65 65 )
66 66 .arg(
67 67 Arg::with_name("deleted")
68 68 .help("show only deleted files")
69 69 .short("-d")
70 70 .long("--deleted"),
71 71 )
72 72 .arg(
73 73 Arg::with_name("unknown")
74 74 .help("show only unknown (not tracked) files")
75 75 .short("-u")
76 76 .long("--unknown"),
77 77 )
78 78 .arg(
79 79 Arg::with_name("ignored")
80 80 .help("show only ignored files")
81 81 .short("-i")
82 82 .long("--ignored"),
83 83 )
84 84 }
85 85
86 86 /// Pure data type allowing the caller to specify file states to display
87 87 #[derive(Copy, Clone, Debug)]
88 88 pub struct DisplayStates {
89 89 pub modified: bool,
90 90 pub added: bool,
91 91 pub removed: bool,
92 92 pub clean: bool,
93 93 pub deleted: bool,
94 94 pub unknown: bool,
95 95 pub ignored: bool,
96 96 }
97 97
98 98 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
99 99 modified: true,
100 100 added: true,
101 101 removed: true,
102 102 clean: false,
103 103 deleted: true,
104 104 unknown: true,
105 105 ignored: false,
106 106 };
107 107
108 108 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
109 109 modified: true,
110 110 added: true,
111 111 removed: true,
112 112 clean: true,
113 113 deleted: true,
114 114 unknown: true,
115 115 ignored: true,
116 116 };
117 117
118 118 impl DisplayStates {
119 119 pub fn is_empty(&self) -> bool {
120 120 !(self.modified
121 121 || self.added
122 122 || self.removed
123 123 || self.clean
124 124 || self.deleted
125 125 || self.unknown
126 126 || self.ignored)
127 127 }
128 128 }
129 129
130 130 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
131 131 let status_enabled_default = false;
132 132 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
133 133 if !status_enabled.unwrap_or(status_enabled_default) {
134 134 return Err(CommandError::unsupported(
135 135 "status is experimental in rhg (enable it with 'rhg.status = true' \
136 136 or enable fallback with 'rhg.on-unsupported = fallback')"
137 137 ));
138 138 }
139 139
140 140 // TODO: lift these limitations
141 141 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
142 142 return Err(CommandError::unsupported(
143 143 "ui.tweakdefaults is not yet supported with rhg status",
144 144 ));
145 145 }
146 146 if invocation.config.get_bool(b"ui", b"statuscopies")? {
147 147 return Err(CommandError::unsupported(
148 148 "ui.statuscopies is not yet supported with rhg status",
149 149 ));
150 150 }
151 if invocation
152 .config
153 .get(b"commands", b"status.terse")
154 .is_some()
155 {
156 return Err(CommandError::unsupported(
157 "status.terse is not yet supported with rhg status",
158 ));
159 }
151 160
152 161 let ui = invocation.ui;
153 162 let config = invocation.config;
154 163 let args = invocation.subcommand_args;
155 164 let display_states = if args.is_present("all") {
156 165 // TODO when implementing `--quiet`: it excludes clean files
157 166 // from `--all`
158 167 ALL_DISPLAY_STATES
159 168 } else {
160 169 let requested = DisplayStates {
161 170 modified: args.is_present("modified"),
162 171 added: args.is_present("added"),
163 172 removed: args.is_present("removed"),
164 173 clean: args.is_present("clean"),
165 174 deleted: args.is_present("deleted"),
166 175 unknown: args.is_present("unknown"),
167 176 ignored: args.is_present("ignored"),
168 177 };
169 178 if requested.is_empty() {
170 179 DEFAULT_DISPLAY_STATES
171 180 } else {
172 181 requested
173 182 }
174 183 };
175 184
176 185 let repo = invocation.repo?;
177 186 let mut dmap = repo.dirstate_map_mut()?;
178 187
179 188 let options = StatusOptions {
180 189 // TODO should be provided by the dirstate parsing and
181 190 // hence be stored on dmap. Using a value that assumes we aren't
182 191 // below the time resolution granularity of the FS and the
183 192 // dirstate.
184 193 last_normal_time: TruncatedTimestamp::new_truncate(0, 0),
185 194 // we're currently supporting file systems with exec flags only
186 195 // anyway
187 196 check_exec: true,
188 197 list_clean: display_states.clean,
189 198 list_unknown: display_states.unknown,
190 199 list_ignored: display_states.ignored,
191 200 collect_traversed_dirs: false,
192 201 };
193 202 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
194 203 let (mut ds_status, pattern_warnings) = dmap.status(
195 204 &AlwaysMatcher,
196 205 repo.working_directory_path().to_owned(),
197 206 vec![ignore_file],
198 207 options,
199 208 )?;
200 209 if !pattern_warnings.is_empty() {
201 210 warn!("Pattern warnings: {:?}", &pattern_warnings);
202 211 }
203 212
204 213 if !ds_status.bad.is_empty() {
205 214 warn!("Bad matches {:?}", &(ds_status.bad))
206 215 }
207 216 if !ds_status.unsure.is_empty() {
208 217 info!(
209 218 "Files to be rechecked by retrieval from filelog: {:?}",
210 219 &ds_status.unsure
211 220 );
212 221 }
213 222 if !ds_status.unsure.is_empty()
214 223 && (display_states.modified || display_states.clean)
215 224 {
216 225 let p1 = repo.dirstate_parents()?.p1;
217 226 let manifest = repo.manifest_for_node(p1).map_err(|e| {
218 227 CommandError::from((e, &*format!("{:x}", p1.short())))
219 228 })?;
220 229 for to_check in ds_status.unsure {
221 230 if cat_file_is_modified(repo, &manifest, &to_check)? {
222 231 if display_states.modified {
223 232 ds_status.modified.push(to_check);
224 233 }
225 234 } else {
226 235 if display_states.clean {
227 236 ds_status.clean.push(to_check);
228 237 }
229 238 }
230 239 }
231 240 }
232 241 if display_states.modified {
233 242 display_status_paths(ui, repo, config, &mut ds_status.modified, b"M")?;
234 243 }
235 244 if display_states.added {
236 245 display_status_paths(ui, repo, config, &mut ds_status.added, b"A")?;
237 246 }
238 247 if display_states.removed {
239 248 display_status_paths(ui, repo, config, &mut ds_status.removed, b"R")?;
240 249 }
241 250 if display_states.deleted {
242 251 display_status_paths(ui, repo, config, &mut ds_status.deleted, b"!")?;
243 252 }
244 253 if display_states.unknown {
245 254 display_status_paths(ui, repo, config, &mut ds_status.unknown, b"?")?;
246 255 }
247 256 if display_states.ignored {
248 257 display_status_paths(ui, repo, config, &mut ds_status.ignored, b"I")?;
249 258 }
250 259 if display_states.clean {
251 260 display_status_paths(ui, repo, config, &mut ds_status.clean, b"C")?;
252 261 }
253 262 Ok(())
254 263 }
255 264
256 265 // Probably more elegant to use a Deref or Borrow trait rather than
257 266 // harcode HgPathBuf, but probably not really useful at this point
258 267 fn display_status_paths(
259 268 ui: &Ui,
260 269 repo: &Repo,
261 270 config: &Config,
262 271 paths: &mut [HgPathCow],
263 272 status_prefix: &[u8],
264 273 ) -> Result<(), CommandError> {
265 274 paths.sort_unstable();
266 275 let mut relative: bool = config.get_bool(b"ui", b"relative-paths")?;
267 276 relative = config
268 277 .get_option(b"commands", b"status.relative")?
269 278 .unwrap_or(relative);
270 279 if relative && !ui.plain() {
271 280 relativize_paths(
272 281 repo,
273 282 paths,
274 283 |path: Cow<[u8]>| -> Result<(), UiError> {
275 284 ui.write_stdout(
276 285 &[status_prefix, b" ", path.as_ref(), b"\n"].concat(),
277 286 )
278 287 },
279 288 )?;
280 289 } else {
281 290 for path in paths {
282 291 // Same TODO as in commands::root
283 292 let bytes: &[u8] = path.as_bytes();
284 293 // TODO optim, probably lots of unneeded copies here, especially
285 294 // if out stream is buffered
286 295 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
287 296 }
288 297 }
289 298 Ok(())
290 299 }
291 300
292 301 /// Check if a file is modified by comparing actual repo store and file system.
293 302 ///
294 303 /// This meant to be used for those that the dirstate cannot resolve, due
295 304 /// to time resolution limits.
296 305 ///
297 306 /// TODO: detect permission bits and similar metadata modifications
298 307 fn cat_file_is_modified(
299 308 repo: &Repo,
300 309 manifest: &Manifest,
301 310 hg_path: &HgPath,
302 311 ) -> Result<bool, HgError> {
303 312 let file_node = manifest
304 313 .find_file(hg_path)?
305 314 .expect("ambgious file not in p1");
306 315 let filelog = repo.filelog(hg_path)?;
307 316 let filelog_entry = filelog.data_for_node(file_node).map_err(|_| {
308 317 HgError::corrupted("filelog missing node from manifest")
309 318 })?;
310 319 let contents_in_p1 = filelog_entry.data()?;
311 320
312 321 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
313 322 let fs_contents = repo.working_directory_vfs().read(fs_path)?;
314 323 return Ok(contents_in_p1 != &*fs_contents);
315 324 }
@@ -1,283 +1,279 b''
1 TODO: fix rhg bugs that make this test fail when status is enabled
2 $ unset RHG_STATUS
3
4
5 1 $ mkdir folder
6 2 $ cd folder
7 3 $ hg init
8 4 $ mkdir x x/l x/m x/n x/l/u x/l/u/a
9 5 $ touch a b x/aa.o x/bb.o
10 6 $ hg status
11 7 ? a
12 8 ? b
13 9 ? x/aa.o
14 10 ? x/bb.o
15 11
16 12 $ hg status --terse u
17 13 ? a
18 14 ? b
19 15 ? x/
20 16 $ hg status --terse maudric
21 17 ? a
22 18 ? b
23 19 ? x/
24 20 $ hg status --terse madric
25 21 ? a
26 22 ? b
27 23 ? x/aa.o
28 24 ? x/bb.o
29 25 $ hg status --terse f
30 26 abort: 'f' not recognized
31 27 [10]
32 28
33 29 Add a .hgignore so that we can also have ignored files
34 30
35 31 $ echo ".*\.o" > .hgignore
36 32 $ hg status
37 33 ? .hgignore
38 34 ? a
39 35 ? b
40 36 $ hg status -i
41 37 I x/aa.o
42 38 I x/bb.o
43 39
44 40 Tersing ignored files
45 41 $ hg status -t i --ignored
46 42 I x/
47 43
48 44 Adding more files
49 45 $ mkdir y
50 46 $ touch x/aa x/bb y/l y/m y/l.o y/m.o
51 47 $ touch x/l/aa x/m/aa x/n/aa x/l/u/bb x/l/u/a/bb
52 48
53 49 $ hg status
54 50 ? .hgignore
55 51 ? a
56 52 ? b
57 53 ? x/aa
58 54 ? x/bb
59 55 ? x/l/aa
60 56 ? x/l/u/a/bb
61 57 ? x/l/u/bb
62 58 ? x/m/aa
63 59 ? x/n/aa
64 60 ? y/l
65 61 ? y/m
66 62
67 63 $ hg status --terse u
68 64 ? .hgignore
69 65 ? a
70 66 ? b
71 67 ? x/
72 68 ? y/
73 69
74 70 Run from subdirectory
75 71 $ hg status --terse u --cwd x/l
76 72 ? .hgignore
77 73 ? a
78 74 ? b
79 75 ? x/
80 76 ? y/
81 77 $ relstatus() {
82 78 > hg status --terse u --config commands.status.relative=1 "$@";
83 79 > }
84 80 This should probably have {"l/", "m/", "n/"} instead of {"."}. They should
85 81 probably come after "../y/".
86 82 $ relstatus --cwd x
87 83 ? ../.hgignore
88 84 ? ../a
89 85 ? ../b
90 86 ? .
91 87 ? ../y/
92 88 This should probably have {"u/", "../m/", "../n/"} instead of {"../"}.
93 89 $ relstatus --cwd x/l
94 90 ? ../../.hgignore
95 91 ? ../../a
96 92 ? ../../b
97 93 ? ../
98 94 ? ../../y/
99 95 This should probably have {"a/", "bb", "../aa", "../../m/", "../../n/"}
100 96 instead of {"../../"}.
101 97 $ relstatus --cwd x/l/u
102 98 ? ../../../.hgignore
103 99 ? ../../../a
104 100 ? ../../../b
105 101 ? ../../
106 102 ? ../../../y/
107 103 This should probably have {"bb", "../bb", "../../aa", "../../../m/",
108 104 "../../../n/"} instead of {"../../../"}.
109 105 $ relstatus --cwd x/l/u/a
110 106 ? ../../../../.hgignore
111 107 ? ../../../../a
112 108 ? ../../../../b
113 109 ? ../../../
114 110 ? ../../../../y/
115 111
116 112 $ hg add x/aa x/bb .hgignore
117 113 $ hg status --terse au
118 114 A .hgignore
119 115 A x/aa
120 116 A x/bb
121 117 ? a
122 118 ? b
123 119 ? x/l/
124 120 ? x/m/
125 121 ? x/n/
126 122 ? y/
127 123
128 124 Including ignored files
129 125
130 126 $ hg status --terse aui
131 127 A .hgignore
132 128 A x/aa
133 129 A x/bb
134 130 ? a
135 131 ? b
136 132 ? x/l/
137 133 ? x/m/
138 134 ? x/n/
139 135 ? y/l
140 136 ? y/m
141 137 $ hg status --terse au -i
142 138 I x/aa.o
143 139 I x/bb.o
144 140 I y/l.o
145 141 I y/m.o
146 142
147 143 Committing some of the files
148 144
149 145 $ hg commit x/aa x/bb .hgignore -m "First commit"
150 146 $ hg status
151 147 ? a
152 148 ? b
153 149 ? x/l/aa
154 150 ? x/l/u/a/bb
155 151 ? x/l/u/bb
156 152 ? x/m/aa
157 153 ? x/n/aa
158 154 ? y/l
159 155 ? y/m
160 156 $ hg status --terse mardu
161 157 ? a
162 158 ? b
163 159 ? x/l/
164 160 ? x/m/
165 161 ? x/n/
166 162 ? y/
167 163
168 164 Modifying already committed files
169 165
170 166 $ echo "Hello" >> x/aa
171 167 $ echo "World" >> x/bb
172 168 $ hg status --terse maurdc
173 169 M x/aa
174 170 M x/bb
175 171 ? a
176 172 ? b
177 173 ? x/l/
178 174 ? x/m/
179 175 ? x/n/
180 176 ? y/
181 177
182 178 Respecting other flags
183 179
184 180 $ hg status --terse marduic --all
185 181 M x/aa
186 182 M x/bb
187 183 ? a
188 184 ? b
189 185 ? x/l/
190 186 ? x/m/
191 187 ? x/n/
192 188 ? y/l
193 189 ? y/m
194 190 I x/aa.o
195 191 I x/bb.o
196 192 I y/l.o
197 193 I y/m.o
198 194 C .hgignore
199 195 $ hg status --terse marduic -a
200 196 $ hg status --terse marduic -c
201 197 C .hgignore
202 198 $ hg status --terse marduic -m
203 199 M x/aa
204 200 M x/bb
205 201
206 202 Passing 'i' in terse value will consider the ignored files while tersing
207 203
208 204 $ hg status --terse marduic -u
209 205 ? a
210 206 ? b
211 207 ? x/l/
212 208 ? x/m/
213 209 ? x/n/
214 210 ? y/l
215 211 ? y/m
216 212
217 213 Omitting 'i' in terse value does not consider ignored files while tersing
218 214
219 215 $ hg status --terse marduc -u
220 216 ? a
221 217 ? b
222 218 ? x/l/
223 219 ? x/m/
224 220 ? x/n/
225 221 ? y/
226 222
227 223 Trying with --rev
228 224
229 225 $ hg status --terse marduic --rev 0 --rev 1
230 226 abort: cannot use --terse with --rev
231 227 [10]
232 228
233 229 Config item to set the default terseness
234 230 $ cat <<EOF >> $HGRCPATH
235 231 > [commands]
236 232 > status.terse = u
237 233 > EOF
238 234 $ hg status -mu
239 235 M x/aa
240 236 M x/bb
241 237 ? a
242 238 ? b
243 239 ? x/l/
244 240 ? x/m/
245 241 ? x/n/
246 242 ? y/
247 243
248 244 Command line flag overrides the default
249 245 $ hg status --terse=
250 246 M x/aa
251 247 M x/bb
252 248 ? a
253 249 ? b
254 250 ? x/l/aa
255 251 ? x/l/u/a/bb
256 252 ? x/l/u/bb
257 253 ? x/m/aa
258 254 ? x/n/aa
259 255 ? y/l
260 256 ? y/m
261 257 $ hg status --terse=mardu
262 258 M x/aa
263 259 M x/bb
264 260 ? a
265 261 ? b
266 262 ? x/l/
267 263 ? x/m/
268 264 ? x/n/
269 265 ? y/
270 266
271 267 Specifying --rev should still work, with the terseness disabled.
272 268 $ hg status --rev 0
273 269 M x/aa
274 270 M x/bb
275 271 ? a
276 272 ? b
277 273 ? x/l/aa
278 274 ? x/l/u/a/bb
279 275 ? x/l/u/bb
280 276 ? x/m/aa
281 277 ? x/n/aa
282 278 ? y/l
283 279 ? y/m
General Comments 0
You need to be logged in to leave comments. Login now