##// END OF EJS Templates
dirstate-tree: Remove newly-empty nodes after removing a `DirstateEntry`...
Simon Sapin -
r47964:47ccab19 default
parent child Browse files
Show More
@@ -1,622 +1,627 b''
1 1 use bytes_cast::BytesCast;
2 2 use micro_timer::timed;
3 3 use std::borrow::Cow;
4 4 use std::convert::TryInto;
5 5 use std::path::PathBuf;
6 6
7 7 use super::path_with_basename::WithBasename;
8 8 use crate::dirstate::parsers::clear_ambiguous_mtime;
9 9 use crate::dirstate::parsers::pack_entry;
10 10 use crate::dirstate::parsers::packed_entry_size;
11 11 use crate::dirstate::parsers::parse_dirstate_entries;
12 12 use crate::dirstate::parsers::Timestamp;
13 13 use crate::matchers::Matcher;
14 14 use crate::utils::hg_path::{HgPath, HgPathBuf};
15 15 use crate::CopyMapIter;
16 16 use crate::DirstateEntry;
17 17 use crate::DirstateError;
18 18 use crate::DirstateMapError;
19 19 use crate::DirstateParents;
20 20 use crate::DirstateStatus;
21 21 use crate::EntryState;
22 22 use crate::FastHashMap;
23 23 use crate::PatternFileWarning;
24 24 use crate::StateMapIter;
25 25 use crate::StatusError;
26 26 use crate::StatusOptions;
27 27
28 28 pub struct DirstateMap<'on_disk> {
29 29 /// Contents of the `.hg/dirstate` file
30 30 on_disk: &'on_disk [u8],
31 31
32 32 pub(super) root: ChildNodes<'on_disk>,
33 33
34 34 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
35 35 nodes_with_entry_count: usize,
36 36
37 37 /// Number of nodes anywhere in the tree that have
38 38 /// `.copy_source.is_some()`.
39 39 nodes_with_copy_source_count: usize,
40 40 }
41 41
42 42 /// Using a plain `HgPathBuf` of the full path from the repository root as a
43 43 /// map key would also work: all paths in a given map have the same parent
44 44 /// path, so comparing full paths gives the same result as comparing base
45 45 /// names. However `BTreeMap` would waste time always re-comparing the same
46 46 /// string prefix.
47 47 pub(super) type ChildNodes<'on_disk> =
48 48 FastHashMap<WithBasename<Cow<'on_disk, HgPath>>, Node<'on_disk>>;
49 49
50 50 /// Represents a file or a directory
51 51 #[derive(Default)]
52 52 pub(super) struct Node<'on_disk> {
53 53 /// `None` for directories
54 54 pub(super) entry: Option<DirstateEntry>,
55 55
56 56 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
57 57
58 58 pub(super) children: ChildNodes<'on_disk>,
59 59
60 60 /// How many (non-inclusive) descendants of this node are tracked files
61 61 tracked_descendants_count: usize,
62 62 }
63 63
64 64 impl Node<'_> {
65 65 pub(super) fn state(&self) -> Option<EntryState> {
66 66 self.entry.as_ref().map(|entry| entry.state)
67 67 }
68 68 }
69 69
70 70 /// `(full_path, entry, copy_source)`
71 71 type NodeDataMut<'tree, 'on_disk> = (
72 72 &'tree HgPath,
73 73 &'tree mut Option<DirstateEntry>,
74 74 &'tree mut Option<Cow<'on_disk, HgPath>>,
75 75 );
76 76
77 77 impl<'on_disk> DirstateMap<'on_disk> {
78 78 pub fn new(
79 79 on_disk: &'on_disk [u8],
80 80 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
81 81 let mut map = Self {
82 82 on_disk,
83 83 root: ChildNodes::default(),
84 84 nodes_with_entry_count: 0,
85 85 nodes_with_copy_source_count: 0,
86 86 };
87 87 let parents = map.read()?;
88 88 Ok((map, parents))
89 89 }
90 90
91 91 /// Should only be called in `new`
92 92 #[timed]
93 93 fn read(&mut self) -> Result<Option<DirstateParents>, DirstateError> {
94 94 if self.on_disk.is_empty() {
95 95 return Ok(None);
96 96 }
97 97
98 98 let parents = parse_dirstate_entries(
99 99 self.on_disk,
100 100 |path, entry, copy_source| {
101 101 let tracked = entry.state.is_tracked();
102 102 let node = Self::get_or_insert_node(
103 103 &mut self.root,
104 104 path,
105 105 WithBasename::to_cow_borrowed,
106 106 |ancestor| {
107 107 if tracked {
108 108 ancestor.tracked_descendants_count += 1
109 109 }
110 110 },
111 111 );
112 112 assert!(
113 113 node.entry.is_none(),
114 114 "duplicate dirstate entry in read"
115 115 );
116 116 assert!(
117 117 node.copy_source.is_none(),
118 118 "duplicate dirstate entry in read"
119 119 );
120 120 node.entry = Some(*entry);
121 121 node.copy_source = copy_source.map(Cow::Borrowed);
122 122 self.nodes_with_entry_count += 1;
123 123 if copy_source.is_some() {
124 124 self.nodes_with_copy_source_count += 1
125 125 }
126 126 },
127 127 )?;
128 128
129 129 Ok(Some(parents.clone()))
130 130 }
131 131
132 132 fn get_node(&self, path: &HgPath) -> Option<&Node> {
133 133 let mut children = &self.root;
134 134 let mut components = path.components();
135 135 let mut component =
136 136 components.next().expect("expected at least one components");
137 137 loop {
138 138 let child = children.get(component)?;
139 139 if let Some(next_component) = components.next() {
140 140 component = next_component;
141 141 children = &child.children;
142 142 } else {
143 143 return Some(child);
144 144 }
145 145 }
146 146 }
147 147
148 148 /// Returns a mutable reference to the node at `path` if it exists
149 149 ///
150 150 /// This takes `root` instead of `&mut self` so that callers can mutate
151 151 /// other fields while the returned borrow is still valid
152 152 fn get_node_mut<'tree>(
153 153 root: &'tree mut ChildNodes<'on_disk>,
154 154 path: &HgPath,
155 155 ) -> Option<&'tree mut Node<'on_disk>> {
156 156 let mut children = root;
157 157 let mut components = path.components();
158 158 let mut component =
159 159 components.next().expect("expected at least one components");
160 160 loop {
161 161 let child = children.get_mut(component)?;
162 162 if let Some(next_component) = components.next() {
163 163 component = next_component;
164 164 children = &mut child.children;
165 165 } else {
166 166 return Some(child);
167 167 }
168 168 }
169 169 }
170 170
171 171 fn get_or_insert_node<'tree, 'path>(
172 172 root: &'tree mut ChildNodes<'on_disk>,
173 173 path: &'path HgPath,
174 174 to_cow: impl Fn(
175 175 WithBasename<&'path HgPath>,
176 176 ) -> WithBasename<Cow<'on_disk, HgPath>>,
177 177 mut each_ancestor: impl FnMut(&mut Node),
178 178 ) -> &'tree mut Node<'on_disk> {
179 179 let mut child_nodes = root;
180 180 let mut inclusive_ancestor_paths =
181 181 WithBasename::inclusive_ancestors_of(path);
182 182 let mut ancestor_path = inclusive_ancestor_paths
183 183 .next()
184 184 .expect("expected at least one inclusive ancestor");
185 185 loop {
186 186 // TODO: can we avoid allocating an owned key in cases where the
187 187 // map already contains that key, without introducing double
188 188 // lookup?
189 189 let child_node =
190 190 child_nodes.entry(to_cow(ancestor_path)).or_default();
191 191 if let Some(next) = inclusive_ancestor_paths.next() {
192 192 each_ancestor(child_node);
193 193 ancestor_path = next;
194 194 child_nodes = &mut child_node.children;
195 195 } else {
196 196 return child_node;
197 197 }
198 198 }
199 199 }
200 200
201 201 fn add_or_remove_file(
202 202 &mut self,
203 203 path: &HgPath,
204 204 old_state: EntryState,
205 205 new_entry: DirstateEntry,
206 206 ) {
207 207 let tracked_count_increment =
208 208 match (old_state.is_tracked(), new_entry.state.is_tracked()) {
209 209 (false, true) => 1,
210 210 (true, false) => -1,
211 211 _ => 0,
212 212 };
213 213
214 214 let node = Self::get_or_insert_node(
215 215 &mut self.root,
216 216 path,
217 217 WithBasename::to_cow_owned,
218 218 |ancestor| {
219 219 // We can’t use `+= increment` because the counter is unsigned,
220 220 // and we want debug builds to detect accidental underflow
221 221 // through zero
222 222 match tracked_count_increment {
223 223 1 => ancestor.tracked_descendants_count += 1,
224 224 -1 => ancestor.tracked_descendants_count -= 1,
225 225 _ => {}
226 226 }
227 227 },
228 228 );
229 229 if node.entry.is_none() {
230 230 self.nodes_with_entry_count += 1
231 231 }
232 232 node.entry = Some(new_entry)
233 233 }
234 234
235 235 fn iter_nodes<'a>(
236 236 &'a self,
237 237 ) -> impl Iterator<Item = (&'a HgPath, &'a Node)> + 'a {
238 238 // Depth first tree traversal.
239 239 //
240 240 // If we could afford internal iteration and recursion,
241 241 // this would look like:
242 242 //
243 243 // ```
244 244 // fn traverse_children(
245 245 // children: &ChildNodes,
246 246 // each: &mut impl FnMut(&Node),
247 247 // ) {
248 248 // for child in children.values() {
249 249 // traverse_children(&child.children, each);
250 250 // each(child);
251 251 // }
252 252 // }
253 253 // ```
254 254 //
255 255 // However we want an external iterator and therefore can’t use the
256 256 // call stack. Use an explicit stack instead:
257 257 let mut stack = Vec::new();
258 258 let mut iter = self.root.iter();
259 259 std::iter::from_fn(move || {
260 260 while let Some((key, child_node)) = iter.next() {
261 261 // Pseudo-recursion
262 262 let new_iter = child_node.children.iter();
263 263 let old_iter = std::mem::replace(&mut iter, new_iter);
264 264 let key = &**key.full_path();
265 265 stack.push((key, child_node, old_iter));
266 266 }
267 267 // Found the end of a `children.iter()` iterator.
268 268 if let Some((key, child_node, next_iter)) = stack.pop() {
269 269 // "Return" from pseudo-recursion by restoring state from the
270 270 // explicit stack
271 271 iter = next_iter;
272 272
273 273 Some((key, child_node))
274 274 } else {
275 275 // Reached the bottom of the stack, we’re done
276 276 None
277 277 }
278 278 })
279 279 }
280 280
281 281 /// Mutable iterator for the `(entry, copy source)` of each node.
282 282 ///
283 283 /// It would not be safe to yield mutable references to nodes themeselves
284 284 /// with `-> impl Iterator<Item = &mut Node>` since child nodes are
285 285 /// reachable from their ancestor nodes, potentially creating multiple
286 286 /// `&mut` references to a given node.
287 287 fn iter_node_data_mut<'tree>(
288 288 &'tree mut self,
289 289 ) -> impl Iterator<Item = NodeDataMut<'tree, 'on_disk>> + 'tree {
290 290 // Explict stack for pseudo-recursion, see `iter_nodes` above.
291 291 let mut stack = Vec::new();
292 292 let mut iter = self.root.iter_mut();
293 293 std::iter::from_fn(move || {
294 294 while let Some((key, child_node)) = iter.next() {
295 295 // Pseudo-recursion
296 296 let data = (
297 297 &**key.full_path(),
298 298 &mut child_node.entry,
299 299 &mut child_node.copy_source,
300 300 );
301 301 let new_iter = child_node.children.iter_mut();
302 302 let old_iter = std::mem::replace(&mut iter, new_iter);
303 303 stack.push((data, old_iter));
304 304 }
305 305 // Found the end of a `children.values_mut()` iterator.
306 306 if let Some((data, next_iter)) = stack.pop() {
307 307 // "Return" from pseudo-recursion by restoring state from the
308 308 // explicit stack
309 309 iter = next_iter;
310 310
311 311 Some(data)
312 312 } else {
313 313 // Reached the bottom of the stack, we’re done
314 314 None
315 315 }
316 316 })
317 317 }
318 318 }
319 319
320 320 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
321 321 fn clear(&mut self) {
322 322 self.root.clear();
323 323 self.nodes_with_entry_count = 0;
324 324 self.nodes_with_copy_source_count = 0;
325 325 }
326 326
327 327 fn add_file(
328 328 &mut self,
329 329 filename: &HgPath,
330 330 old_state: EntryState,
331 331 entry: DirstateEntry,
332 332 ) -> Result<(), DirstateMapError> {
333 333 self.add_or_remove_file(filename, old_state, entry);
334 334 Ok(())
335 335 }
336 336
337 337 fn remove_file(
338 338 &mut self,
339 339 filename: &HgPath,
340 340 old_state: EntryState,
341 341 size: i32,
342 342 ) -> Result<(), DirstateMapError> {
343 343 let entry = DirstateEntry {
344 344 state: EntryState::Removed,
345 345 mode: 0,
346 346 size,
347 347 mtime: 0,
348 348 };
349 349 self.add_or_remove_file(filename, old_state, entry);
350 350 Ok(())
351 351 }
352 352
353 353 fn drop_file(
354 354 &mut self,
355 355 filename: &HgPath,
356 356 old_state: EntryState,
357 357 ) -> Result<bool, DirstateMapError> {
358 358 struct Dropped {
359 359 was_tracked: bool,
360 360 had_entry: bool,
361 361 had_copy_source: bool,
362 362 }
363 363 fn recur(nodes: &mut ChildNodes, path: &HgPath) -> Option<Dropped> {
364 364 let (first_path_component, rest_of_path) =
365 365 path.split_first_component();
366 366 let node = nodes.get_mut(first_path_component)?;
367 367 let dropped;
368 368 if let Some(rest) = rest_of_path {
369 369 dropped = recur(&mut node.children, rest)?;
370 370 if dropped.was_tracked {
371 371 node.tracked_descendants_count -= 1;
372 372 }
373 373 } else {
374 374 dropped = Dropped {
375 375 was_tracked: node
376 376 .entry
377 377 .as_ref()
378 378 .map_or(false, |entry| entry.state.is_tracked()),
379 379 had_entry: node.entry.take().is_some(),
380 380 had_copy_source: node.copy_source.take().is_some(),
381 381 };
382 // TODO: this leaves in the tree a "non-file" node. Should we
383 // remove the node instead, together with ancestor nodes for
384 // directories that become empty?
382 }
383 // After recursion, for both leaf (rest_of_path is None) nodes and
384 // parent nodes, remove a node if it just became empty.
385 if node.entry.is_none()
386 && node.copy_source.is_none()
387 && node.children.is_empty()
388 {
389 nodes.remove(first_path_component);
385 390 }
386 391 Some(dropped)
387 392 }
388 393
389 394 if let Some(dropped) = recur(&mut self.root, filename) {
390 395 if dropped.had_entry {
391 396 self.nodes_with_entry_count -= 1
392 397 }
393 398 if dropped.had_copy_source {
394 399 self.nodes_with_copy_source_count -= 1
395 400 }
396 401 Ok(dropped.had_entry)
397 402 } else {
398 403 debug_assert!(!old_state.is_tracked());
399 404 Ok(false)
400 405 }
401 406 }
402 407
403 408 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
404 409 for filename in filenames {
405 410 if let Some(node) = Self::get_node_mut(&mut self.root, &filename) {
406 411 if let Some(entry) = node.entry.as_mut() {
407 412 clear_ambiguous_mtime(entry, now);
408 413 }
409 414 }
410 415 }
411 416 }
412 417
413 418 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
414 419 self.get_node(key)
415 420 .and_then(|node| node.entry.as_ref())
416 421 .map_or(false, DirstateEntry::is_non_normal)
417 422 }
418 423
419 424 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
420 425 // Do nothing, this `DirstateMap` does not have a separate "non normal
421 426 // entries" set that need to be kept up to date
422 427 }
423 428
424 429 fn non_normal_or_other_parent_paths(
425 430 &mut self,
426 431 ) -> Box<dyn Iterator<Item = &HgPath> + '_> {
427 432 Box::new(self.iter_nodes().filter_map(|(path, node)| {
428 433 node.entry
429 434 .as_ref()
430 435 .filter(|entry| {
431 436 entry.is_non_normal() || entry.is_from_other_parent()
432 437 })
433 438 .map(|_| path)
434 439 }))
435 440 }
436 441
437 442 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
438 443 // Do nothing, this `DirstateMap` does not have a separate "non normal
439 444 // entries" and "from other parent" sets that need to be recomputed
440 445 }
441 446
442 447 fn iter_non_normal_paths(
443 448 &mut self,
444 449 ) -> Box<dyn Iterator<Item = &HgPath> + Send + '_> {
445 450 self.iter_non_normal_paths_panic()
446 451 }
447 452
448 453 fn iter_non_normal_paths_panic(
449 454 &self,
450 455 ) -> Box<dyn Iterator<Item = &HgPath> + Send + '_> {
451 456 Box::new(self.iter_nodes().filter_map(|(path, node)| {
452 457 node.entry
453 458 .as_ref()
454 459 .filter(|entry| entry.is_non_normal())
455 460 .map(|_| path)
456 461 }))
457 462 }
458 463
459 464 fn iter_other_parent_paths(
460 465 &mut self,
461 466 ) -> Box<dyn Iterator<Item = &HgPath> + Send + '_> {
462 467 Box::new(self.iter_nodes().filter_map(|(path, node)| {
463 468 node.entry
464 469 .as_ref()
465 470 .filter(|entry| entry.is_from_other_parent())
466 471 .map(|_| path)
467 472 }))
468 473 }
469 474
470 475 fn has_tracked_dir(
471 476 &mut self,
472 477 directory: &HgPath,
473 478 ) -> Result<bool, DirstateMapError> {
474 479 if let Some(node) = self.get_node(directory) {
475 480 // A node without a `DirstateEntry` was created to hold child
476 481 // nodes, and is therefore a directory.
477 482 Ok(node.entry.is_none() && node.tracked_descendants_count > 0)
478 483 } else {
479 484 Ok(false)
480 485 }
481 486 }
482 487
483 488 fn has_dir(
484 489 &mut self,
485 490 directory: &HgPath,
486 491 ) -> Result<bool, DirstateMapError> {
487 492 if let Some(node) = self.get_node(directory) {
488 493 // A node without a `DirstateEntry` was created to hold child
489 494 // nodes, and is therefore a directory.
490 495 Ok(node.entry.is_none())
491 496 } else {
492 497 Ok(false)
493 498 }
494 499 }
495 500
496 501 fn pack(
497 502 &mut self,
498 503 parents: DirstateParents,
499 504 now: Timestamp,
500 505 ) -> Result<Vec<u8>, DirstateError> {
501 506 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
502 507 // reallocations
503 508 let mut size = parents.as_bytes().len();
504 509 for (path, node) in self.iter_nodes() {
505 510 if node.entry.is_some() {
506 511 size += packed_entry_size(
507 512 path,
508 513 node.copy_source.as_ref().map(|p| &**p),
509 514 )
510 515 }
511 516 }
512 517
513 518 let mut packed = Vec::with_capacity(size);
514 519 packed.extend(parents.as_bytes());
515 520
516 521 let now: i32 = now.0.try_into().expect("time overflow");
517 522 for (path, opt_entry, copy_source) in self.iter_node_data_mut() {
518 523 if let Some(entry) = opt_entry {
519 524 clear_ambiguous_mtime(entry, now);
520 525 pack_entry(
521 526 path,
522 527 entry,
523 528 copy_source.as_ref().map(|p| &**p),
524 529 &mut packed,
525 530 );
526 531 }
527 532 }
528 533 Ok(packed)
529 534 }
530 535
531 536 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
532 537 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
533 538 // needs to be recomputed
534 539 Ok(())
535 540 }
536 541
537 542 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
538 543 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
539 544 // to be recomputed
540 545 Ok(())
541 546 }
542 547
543 548 fn status<'a>(
544 549 &'a mut self,
545 550 matcher: &'a (dyn Matcher + Sync),
546 551 root_dir: PathBuf,
547 552 ignore_files: Vec<PathBuf>,
548 553 options: StatusOptions,
549 554 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
550 555 {
551 556 super::status::status(self, matcher, root_dir, ignore_files, options)
552 557 }
553 558
554 559 fn copy_map_len(&self) -> usize {
555 560 self.nodes_with_copy_source_count
556 561 }
557 562
558 563 fn copy_map_iter(&self) -> CopyMapIter<'_> {
559 564 Box::new(self.iter_nodes().filter_map(|(path, node)| {
560 565 node.copy_source
561 566 .as_ref()
562 567 .map(|copy_source| (path, &**copy_source))
563 568 }))
564 569 }
565 570
566 571 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
567 572 if let Some(node) = self.get_node(key) {
568 573 node.copy_source.is_some()
569 574 } else {
570 575 false
571 576 }
572 577 }
573 578
574 579 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPath> {
575 580 self.get_node(key)?.copy_source.as_ref().map(|p| &**p)
576 581 }
577 582
578 583 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
579 584 let count = &mut self.nodes_with_copy_source_count;
580 585 Self::get_node_mut(&mut self.root, key).and_then(|node| {
581 586 if node.copy_source.is_some() {
582 587 *count -= 1
583 588 }
584 589 node.copy_source.take().map(Cow::into_owned)
585 590 })
586 591 }
587 592
588 593 fn copy_map_insert(
589 594 &mut self,
590 595 key: HgPathBuf,
591 596 value: HgPathBuf,
592 597 ) -> Option<HgPathBuf> {
593 598 let node = Self::get_or_insert_node(
594 599 &mut self.root,
595 600 &key,
596 601 WithBasename::to_cow_owned,
597 602 |_ancestor| {},
598 603 );
599 604 if node.copy_source.is_none() {
600 605 self.nodes_with_copy_source_count += 1
601 606 }
602 607 node.copy_source.replace(value.into()).map(Cow::into_owned)
603 608 }
604 609
605 610 fn len(&self) -> usize {
606 611 self.nodes_with_entry_count
607 612 }
608 613
609 614 fn contains_key(&self, key: &HgPath) -> bool {
610 615 self.get(key).is_some()
611 616 }
612 617
613 618 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
614 619 self.get_node(key)?.entry.as_ref()
615 620 }
616 621
617 622 fn iter(&self) -> StateMapIter<'_> {
618 623 Box::new(self.iter_nodes().filter_map(|(path, node)| {
619 624 node.entry.as_ref().map(|entry| (path, entry))
620 625 }))
621 626 }
622 627 }
@@ -1,868 +1,871 b''
1 1 #testcases dirstate-v1 dirstate-v1-tree
2 2
3 3 #if dirstate-v1-tree
4 4 #require rust
5 5 $ echo '[experimental]' >> $HGRCPATH
6 6 $ echo 'dirstate-tree.in-memory=1' >> $HGRCPATH
7 7 #endif
8 8
9 9 $ hg init repo1
10 10 $ cd repo1
11 11 $ mkdir a b a/1 b/1 b/2
12 12 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
13 13
14 14 hg status in repo root:
15 15
16 16 $ hg status
17 17 ? a/1/in_a_1
18 18 ? a/in_a
19 19 ? b/1/in_b_1
20 20 ? b/2/in_b_2
21 21 ? b/in_b
22 22 ? in_root
23 23
24 24 hg status . in repo root:
25 25
26 26 $ hg status .
27 27 ? a/1/in_a_1
28 28 ? a/in_a
29 29 ? b/1/in_b_1
30 30 ? b/2/in_b_2
31 31 ? b/in_b
32 32 ? in_root
33 33
34 34 $ hg status --cwd a
35 35 ? a/1/in_a_1
36 36 ? a/in_a
37 37 ? b/1/in_b_1
38 38 ? b/2/in_b_2
39 39 ? b/in_b
40 40 ? in_root
41 41 $ hg status --cwd a .
42 42 ? 1/in_a_1
43 43 ? in_a
44 44 $ hg status --cwd a ..
45 45 ? 1/in_a_1
46 46 ? in_a
47 47 ? ../b/1/in_b_1
48 48 ? ../b/2/in_b_2
49 49 ? ../b/in_b
50 50 ? ../in_root
51 51
52 52 $ hg status --cwd b
53 53 ? a/1/in_a_1
54 54 ? a/in_a
55 55 ? b/1/in_b_1
56 56 ? b/2/in_b_2
57 57 ? b/in_b
58 58 ? in_root
59 59 $ hg status --cwd b .
60 60 ? 1/in_b_1
61 61 ? 2/in_b_2
62 62 ? in_b
63 63 $ hg status --cwd b ..
64 64 ? ../a/1/in_a_1
65 65 ? ../a/in_a
66 66 ? 1/in_b_1
67 67 ? 2/in_b_2
68 68 ? in_b
69 69 ? ../in_root
70 70
71 71 $ hg status --cwd a/1
72 72 ? a/1/in_a_1
73 73 ? a/in_a
74 74 ? b/1/in_b_1
75 75 ? b/2/in_b_2
76 76 ? b/in_b
77 77 ? in_root
78 78 $ hg status --cwd a/1 .
79 79 ? in_a_1
80 80 $ hg status --cwd a/1 ..
81 81 ? in_a_1
82 82 ? ../in_a
83 83
84 84 $ hg status --cwd b/1
85 85 ? a/1/in_a_1
86 86 ? a/in_a
87 87 ? b/1/in_b_1
88 88 ? b/2/in_b_2
89 89 ? b/in_b
90 90 ? in_root
91 91 $ hg status --cwd b/1 .
92 92 ? in_b_1
93 93 $ hg status --cwd b/1 ..
94 94 ? in_b_1
95 95 ? ../2/in_b_2
96 96 ? ../in_b
97 97
98 98 $ hg status --cwd b/2
99 99 ? a/1/in_a_1
100 100 ? a/in_a
101 101 ? b/1/in_b_1
102 102 ? b/2/in_b_2
103 103 ? b/in_b
104 104 ? in_root
105 105 $ hg status --cwd b/2 .
106 106 ? in_b_2
107 107 $ hg status --cwd b/2 ..
108 108 ? ../1/in_b_1
109 109 ? in_b_2
110 110 ? ../in_b
111 111
112 112 combining patterns with root and patterns without a root works
113 113
114 114 $ hg st a/in_a re:.*b$
115 115 ? a/in_a
116 116 ? b/in_b
117 117
118 118 tweaking defaults works
119 119 $ hg status --cwd a --config ui.tweakdefaults=yes
120 120 ? 1/in_a_1
121 121 ? in_a
122 122 ? ../b/1/in_b_1
123 123 ? ../b/2/in_b_2
124 124 ? ../b/in_b
125 125 ? ../in_root
126 126 $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
127 127 ? a/1/in_a_1 (glob)
128 128 ? a/in_a (glob)
129 129 ? b/1/in_b_1 (glob)
130 130 ? b/2/in_b_2 (glob)
131 131 ? b/in_b (glob)
132 132 ? in_root
133 133 $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
134 134 ? 1/in_a_1
135 135 ? in_a
136 136 ? ../b/1/in_b_1
137 137 ? ../b/2/in_b_2
138 138 ? ../b/in_b
139 139 ? ../in_root (glob)
140 140
141 141 relative paths can be requested
142 142
143 143 $ hg status --cwd a --config ui.relative-paths=yes
144 144 ? 1/in_a_1
145 145 ? in_a
146 146 ? ../b/1/in_b_1
147 147 ? ../b/2/in_b_2
148 148 ? ../b/in_b
149 149 ? ../in_root
150 150
151 151 $ hg status --cwd a . --config ui.relative-paths=legacy
152 152 ? 1/in_a_1
153 153 ? in_a
154 154 $ hg status --cwd a . --config ui.relative-paths=no
155 155 ? a/1/in_a_1
156 156 ? a/in_a
157 157
158 158 commands.status.relative overrides ui.relative-paths
159 159
160 160 $ cat >> $HGRCPATH <<EOF
161 161 > [ui]
162 162 > relative-paths = False
163 163 > [commands]
164 164 > status.relative = True
165 165 > EOF
166 166 $ hg status --cwd a
167 167 ? 1/in_a_1
168 168 ? in_a
169 169 ? ../b/1/in_b_1
170 170 ? ../b/2/in_b_2
171 171 ? ../b/in_b
172 172 ? ../in_root
173 173 $ HGPLAIN=1 hg status --cwd a
174 174 ? a/1/in_a_1 (glob)
175 175 ? a/in_a (glob)
176 176 ? b/1/in_b_1 (glob)
177 177 ? b/2/in_b_2 (glob)
178 178 ? b/in_b (glob)
179 179 ? in_root
180 180
181 181 if relative paths are explicitly off, tweakdefaults doesn't change it
182 182 $ cat >> $HGRCPATH <<EOF
183 183 > [commands]
184 184 > status.relative = False
185 185 > EOF
186 186 $ hg status --cwd a --config ui.tweakdefaults=yes
187 187 ? a/1/in_a_1
188 188 ? a/in_a
189 189 ? b/1/in_b_1
190 190 ? b/2/in_b_2
191 191 ? b/in_b
192 192 ? in_root
193 193
194 194 $ cd ..
195 195
196 196 $ hg init repo2
197 197 $ cd repo2
198 198 $ touch modified removed deleted ignored
199 199 $ echo "^ignored$" > .hgignore
200 200 $ hg ci -A -m 'initial checkin'
201 201 adding .hgignore
202 202 adding deleted
203 203 adding modified
204 204 adding removed
205 205 $ touch modified added unknown ignored
206 206 $ hg add added
207 207 $ hg remove removed
208 208 $ rm deleted
209 209
210 210 hg status:
211 211
212 212 $ hg status
213 213 A added
214 214 R removed
215 215 ! deleted
216 216 ? unknown
217 217
218 218 hg status modified added removed deleted unknown never-existed ignored:
219 219
220 220 $ hg status modified added removed deleted unknown never-existed ignored
221 221 never-existed: * (glob)
222 222 A added
223 223 R removed
224 224 ! deleted
225 225 ? unknown
226 226
227 227 $ hg copy modified copied
228 228
229 229 hg status -C:
230 230
231 231 $ hg status -C
232 232 A added
233 233 A copied
234 234 modified
235 235 R removed
236 236 ! deleted
237 237 ? unknown
238 238
239 239 hg status -A:
240 240
241 241 $ hg status -A
242 242 A added
243 243 A copied
244 244 modified
245 245 R removed
246 246 ! deleted
247 247 ? unknown
248 248 I ignored
249 249 C .hgignore
250 250 C modified
251 251
252 252 $ hg status -A -T '{status} {path} {node|shortest}\n'
253 253 A added ffff
254 254 A copied ffff
255 255 R removed ffff
256 256 ! deleted ffff
257 257 ? unknown ffff
258 258 I ignored ffff
259 259 C .hgignore ffff
260 260 C modified ffff
261 261
262 262 $ hg status -A -Tjson
263 263 [
264 264 {
265 265 "itemtype": "file",
266 266 "path": "added",
267 267 "status": "A"
268 268 },
269 269 {
270 270 "itemtype": "file",
271 271 "path": "copied",
272 272 "source": "modified",
273 273 "status": "A"
274 274 },
275 275 {
276 276 "itemtype": "file",
277 277 "path": "removed",
278 278 "status": "R"
279 279 },
280 280 {
281 281 "itemtype": "file",
282 282 "path": "deleted",
283 283 "status": "!"
284 284 },
285 285 {
286 286 "itemtype": "file",
287 287 "path": "unknown",
288 288 "status": "?"
289 289 },
290 290 {
291 291 "itemtype": "file",
292 292 "path": "ignored",
293 293 "status": "I"
294 294 },
295 295 {
296 296 "itemtype": "file",
297 297 "path": ".hgignore",
298 298 "status": "C"
299 299 },
300 300 {
301 301 "itemtype": "file",
302 302 "path": "modified",
303 303 "status": "C"
304 304 }
305 305 ]
306 306
307 307 $ hg status -A -Tpickle > pickle
308 308 >>> from __future__ import print_function
309 309 >>> from mercurial import util
310 310 >>> pickle = util.pickle
311 311 >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
312 312 >>> for s, p in data: print("%s %s" % (s, p))
313 313 ! deleted
314 314 ? pickle
315 315 ? unknown
316 316 A added
317 317 A copied
318 318 C .hgignore
319 319 C modified
320 320 I ignored
321 321 R removed
322 322 $ rm pickle
323 323
324 324 $ echo "^ignoreddir$" > .hgignore
325 325 $ mkdir ignoreddir
326 326 $ touch ignoreddir/file
327 327
328 328 Test templater support:
329 329
330 330 $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
331 331 [M] .hgignore
332 332 [A] added
333 333 [A] modified -> copied
334 334 [R] removed
335 335 [!] deleted
336 336 [?] ignored
337 337 [?] unknown
338 338 [I] ignoreddir/file
339 339 [C] modified
340 340 $ hg status -AT default
341 341 M .hgignore
342 342 A added
343 343 A copied
344 344 modified
345 345 R removed
346 346 ! deleted
347 347 ? ignored
348 348 ? unknown
349 349 I ignoreddir/file
350 350 C modified
351 351 $ hg status -T compact
352 352 abort: "status" not in template map
353 353 [255]
354 354
355 355 hg status ignoreddir/file:
356 356
357 357 $ hg status ignoreddir/file
358 358
359 359 hg status -i ignoreddir/file:
360 360
361 361 $ hg status -i ignoreddir/file
362 362 I ignoreddir/file
363 363 $ cd ..
364 364
365 365 Check 'status -q' and some combinations
366 366
367 367 $ hg init repo3
368 368 $ cd repo3
369 369 $ touch modified removed deleted ignored
370 370 $ echo "^ignored$" > .hgignore
371 371 $ hg commit -A -m 'initial checkin'
372 372 adding .hgignore
373 373 adding deleted
374 374 adding modified
375 375 adding removed
376 376 $ touch added unknown ignored
377 377 $ hg add added
378 378 $ echo "test" >> modified
379 379 $ hg remove removed
380 380 $ rm deleted
381 381 $ hg copy modified copied
382 382
383 383 Specify working directory revision explicitly, that should be the same as
384 384 "hg status"
385 385
386 386 $ hg status --change "wdir()"
387 387 M modified
388 388 A added
389 389 A copied
390 390 R removed
391 391 ! deleted
392 392 ? unknown
393 393
394 394 Run status with 2 different flags.
395 395 Check if result is the same or different.
396 396 If result is not as expected, raise error
397 397
398 398 $ assert() {
399 399 > hg status $1 > ../a
400 400 > hg status $2 > ../b
401 401 > if diff ../a ../b > /dev/null; then
402 402 > out=0
403 403 > else
404 404 > out=1
405 405 > fi
406 406 > if [ $3 -eq 0 ]; then
407 407 > df="same"
408 408 > else
409 409 > df="different"
410 410 > fi
411 411 > if [ $out -ne $3 ]; then
412 412 > echo "Error on $1 and $2, should be $df."
413 413 > fi
414 414 > }
415 415
416 416 Assert flag1 flag2 [0-same | 1-different]
417 417
418 418 $ assert "-q" "-mard" 0
419 419 $ assert "-A" "-marduicC" 0
420 420 $ assert "-qA" "-mardcC" 0
421 421 $ assert "-qAui" "-A" 0
422 422 $ assert "-qAu" "-marducC" 0
423 423 $ assert "-qAi" "-mardicC" 0
424 424 $ assert "-qu" "-u" 0
425 425 $ assert "-q" "-u" 1
426 426 $ assert "-m" "-a" 1
427 427 $ assert "-r" "-d" 1
428 428 $ cd ..
429 429
430 430 $ hg init repo4
431 431 $ cd repo4
432 432 $ touch modified removed deleted
433 433 $ hg ci -q -A -m 'initial checkin'
434 434 $ touch added unknown
435 435 $ hg add added
436 436 $ hg remove removed
437 437 $ rm deleted
438 438 $ echo x > modified
439 439 $ hg copy modified copied
440 440 $ hg ci -m 'test checkin' -d "1000001 0"
441 441 $ rm *
442 442 $ touch unrelated
443 443 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
444 444
445 445 hg status --change 1:
446 446
447 447 $ hg status --change 1
448 448 M modified
449 449 A added
450 450 A copied
451 451 R removed
452 452
453 453 hg status --change 1 unrelated:
454 454
455 455 $ hg status --change 1 unrelated
456 456
457 457 hg status -C --change 1 added modified copied removed deleted:
458 458
459 459 $ hg status -C --change 1 added modified copied removed deleted
460 460 M modified
461 461 A added
462 462 A copied
463 463 modified
464 464 R removed
465 465
466 466 hg status -A --change 1 and revset:
467 467
468 468 $ hg status -A --change '1|1'
469 469 M modified
470 470 A added
471 471 A copied
472 472 modified
473 473 R removed
474 474 C deleted
475 475
476 476 $ cd ..
477 477
478 478 hg status with --rev and reverted changes:
479 479
480 480 $ hg init reverted-changes-repo
481 481 $ cd reverted-changes-repo
482 482 $ echo a > file
483 483 $ hg add file
484 484 $ hg ci -m a
485 485 $ echo b > file
486 486 $ hg ci -m b
487 487
488 488 reverted file should appear clean
489 489
490 490 $ hg revert -r 0 .
491 491 reverting file
492 492 $ hg status -A --rev 0
493 493 C file
494 494
495 495 #if execbit
496 496 reverted file with changed flag should appear modified
497 497
498 498 $ chmod +x file
499 499 $ hg status -A --rev 0
500 500 M file
501 501
502 502 $ hg revert -r 0 .
503 503 reverting file
504 504
505 505 reverted and committed file with changed flag should appear modified
506 506
507 507 $ hg co -C .
508 508 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
509 509 $ chmod +x file
510 510 $ hg ci -m 'change flag'
511 511 $ hg status -A --rev 1 --rev 2
512 512 M file
513 513 $ hg diff -r 1 -r 2
514 514
515 515 #endif
516 516
517 517 $ cd ..
518 518
519 519 hg status of binary file starting with '\1\n', a separator for metadata:
520 520
521 521 $ hg init repo5
522 522 $ cd repo5
523 523 >>> open("010a", r"wb").write(b"\1\nfoo") and None
524 524 $ hg ci -q -A -m 'initial checkin'
525 525 $ hg status -A
526 526 C 010a
527 527
528 528 >>> open("010a", r"wb").write(b"\1\nbar") and None
529 529 $ hg status -A
530 530 M 010a
531 531 $ hg ci -q -m 'modify 010a'
532 532 $ hg status -A --rev 0:1
533 533 M 010a
534 534
535 535 $ touch empty
536 536 $ hg ci -q -A -m 'add another file'
537 537 $ hg status -A --rev 1:2 010a
538 538 C 010a
539 539
540 540 $ cd ..
541 541
542 542 test "hg status" with "directory pattern" which matches against files
543 543 only known on target revision.
544 544
545 545 $ hg init repo6
546 546 $ cd repo6
547 547
548 548 $ echo a > a.txt
549 549 $ hg add a.txt
550 550 $ hg commit -m '#0'
551 551 $ mkdir -p 1/2/3/4/5
552 552 $ echo b > 1/2/3/4/5/b.txt
553 553 $ hg add 1/2/3/4/5/b.txt
554 554 $ hg commit -m '#1'
555 555
556 556 $ hg update -C 0 > /dev/null
557 557 $ hg status -A
558 558 C a.txt
559 559
560 560 the directory matching against specified pattern should be removed,
561 561 because directory existence prevents 'dirstate.walk()' from showing
562 562 warning message about such pattern.
563 563
564 564 $ test ! -d 1
565 565 $ hg status -A --rev 1 1/2/3/4/5/b.txt
566 566 R 1/2/3/4/5/b.txt
567 567 $ hg status -A --rev 1 1/2/3/4/5
568 568 R 1/2/3/4/5/b.txt
569 569 $ hg status -A --rev 1 1/2/3
570 570 R 1/2/3/4/5/b.txt
571 571 $ hg status -A --rev 1 1
572 572 R 1/2/3/4/5/b.txt
573 573
574 574 $ hg status --config ui.formatdebug=True --rev 1 1
575 575 status = [
576 576 {
577 577 'itemtype': 'file',
578 578 'path': '1/2/3/4/5/b.txt',
579 579 'status': 'R'
580 580 },
581 581 ]
582 582
583 583 #if windows
584 584 $ hg --config ui.slash=false status -A --rev 1 1
585 585 R 1\2\3\4\5\b.txt
586 586 #endif
587 587
588 588 $ cd ..
589 589
590 590 Status after move overwriting a file (issue4458)
591 591 =================================================
592 592
593 593
594 594 $ hg init issue4458
595 595 $ cd issue4458
596 596 $ echo a > a
597 597 $ echo b > b
598 598 $ hg commit -Am base
599 599 adding a
600 600 adding b
601 601
602 602
603 603 with --force
604 604
605 605 $ hg mv b --force a
606 606 $ hg st --copies
607 607 M a
608 608 b
609 609 R b
610 610 $ hg revert --all
611 611 reverting a
612 612 undeleting b
613 613 $ rm *.orig
614 614
615 615 without force
616 616
617 617 $ hg rm a
618 618 $ hg st --copies
619 619 R a
620 620 $ hg mv b a
621 621 $ hg st --copies
622 622 M a
623 623 b
624 624 R b
625 625
626 626 using ui.statuscopies setting
627 627 $ hg st --config ui.statuscopies=true
628 628 M a
629 629 b
630 630 R b
631 631 $ hg st --config ui.statuscopies=false
632 632 M a
633 633 R b
634 634 $ hg st --config ui.tweakdefaults=yes
635 635 M a
636 636 b
637 637 R b
638 638
639 639 using log status template (issue5155)
640 640 $ hg log -Tstatus -r 'wdir()' -C
641 641 changeset: 2147483647:ffffffffffff
642 642 parent: 0:8c55c58b4c0e
643 643 user: test
644 644 date: * (glob)
645 645 files:
646 646 M a
647 647 b
648 648 R b
649 649
650 650 $ hg log -GTstatus -r 'wdir()' -C
651 651 o changeset: 2147483647:ffffffffffff
652 652 | parent: 0:8c55c58b4c0e
653 653 ~ user: test
654 654 date: * (glob)
655 655 files:
656 656 M a
657 657 b
658 658 R b
659 659
660 660
661 661 Other "bug" highlight, the revision status does not report the copy information.
662 662 This is buggy behavior.
663 663
664 664 $ hg commit -m 'blah'
665 665 $ hg st --copies --change .
666 666 M a
667 667 R b
668 668
669 669 using log status template, the copy information is displayed correctly.
670 670 $ hg log -Tstatus -r. -C
671 671 changeset: 1:6685fde43d21
672 672 tag: tip
673 673 user: test
674 674 date: * (glob)
675 675 summary: blah
676 676 files:
677 677 M a
678 678 b
679 679 R b
680 680
681 681
682 682 $ cd ..
683 683
684 684 Make sure .hg doesn't show up even as a symlink
685 685
686 686 $ hg init repo0
687 687 $ mkdir symlink-repo0
688 688 $ cd symlink-repo0
689 689 $ ln -s ../repo0/.hg
690 690 $ hg status
691 691
692 692 If the size hasnt changed but mtime has, status needs to read the contents
693 693 of the file to check whether it has changed
694 694
695 695 $ echo 1 > a
696 696 $ echo 1 > b
697 697 $ touch -t 200102030000 a b
698 698 $ hg commit -Aqm '#0'
699 699 $ echo 2 > a
700 700 $ touch -t 200102040000 a b
701 701 $ hg status
702 702 M a
703 703
704 704 Asking specifically for the status of a deleted/removed file
705 705
706 706 $ rm a
707 707 $ rm b
708 708 $ hg status a
709 709 ! a
710 710 $ hg rm a
711 711 $ hg rm b
712 712 $ hg status a
713 713 R a
714 $ hg commit -qm '#1'
715 $ hg status a
716 a: $ENOENT$
714 717
715 718 Check using include flag with pattern when status does not need to traverse
716 719 the working directory (issue6483)
717 720
718 721 $ cd ..
719 722 $ hg init issue6483
720 723 $ cd issue6483
721 724 $ touch a.py b.rs
722 725 $ hg add a.py b.rs
723 726 $ hg st -aI "*.py"
724 727 A a.py
725 728
726 729 Also check exclude pattern
727 730
728 731 $ hg st -aX "*.rs"
729 732 A a.py
730 733
731 734 issue6335
732 735 When a directory containing a tracked file gets symlinked, as of 5.8
733 736 `hg st` only gives the correct answer about clean (or deleted) files
734 737 if also listing unknowns.
735 738 The tree-based dirstate and status algorithm fix this:
736 739
737 740 #if symlink no-dirstate-v1
738 741
739 742 $ cd ..
740 743 $ hg init issue6335
741 744 $ cd issue6335
742 745 $ mkdir foo
743 746 $ touch foo/a
744 747 $ hg ci -Ama
745 748 adding foo/a
746 749 $ mv foo bar
747 750 $ ln -s bar foo
748 751 $ hg status
749 752 ! foo/a
750 753 ? bar/a
751 754 ? foo
752 755
753 756 $ hg status -c # incorrect output with `dirstate-v1`
754 757 $ hg status -cu
755 758 ? bar/a
756 759 ? foo
757 760 $ hg status -d # incorrect output with `dirstate-v1`
758 761 ! foo/a
759 762 $ hg status -du
760 763 ! foo/a
761 764 ? bar/a
762 765 ? foo
763 766
764 767 #endif
765 768
766 769
767 770 Create a repo with files in each possible status
768 771
769 772 $ cd ..
770 773 $ hg init repo7
771 774 $ cd repo7
772 775 $ mkdir subdir
773 776 $ touch clean modified deleted removed
774 777 $ touch subdir/clean subdir/modified subdir/deleted subdir/removed
775 778 $ echo ignored > .hgignore
776 779 $ hg ci -Aqm '#0'
777 780 $ echo 1 > modified
778 781 $ echo 1 > subdir/modified
779 782 $ rm deleted
780 783 $ rm subdir/deleted
781 784 $ hg rm removed
782 785 $ hg rm subdir/removed
783 786 $ touch unknown ignored
784 787 $ touch subdir/unknown subdir/ignored
785 788
786 789 Check the output
787 790
788 791 $ hg status
789 792 M modified
790 793 M subdir/modified
791 794 R removed
792 795 R subdir/removed
793 796 ! deleted
794 797 ! subdir/deleted
795 798 ? subdir/unknown
796 799 ? unknown
797 800
798 801 $ hg status -mard
799 802 M modified
800 803 M subdir/modified
801 804 R removed
802 805 R subdir/removed
803 806 ! deleted
804 807 ! subdir/deleted
805 808
806 809 $ hg status -A
807 810 M modified
808 811 M subdir/modified
809 812 R removed
810 813 R subdir/removed
811 814 ! deleted
812 815 ! subdir/deleted
813 816 ? subdir/unknown
814 817 ? unknown
815 818 I ignored
816 819 I subdir/ignored
817 820 C .hgignore
818 821 C clean
819 822 C subdir/clean
820 823
821 824 Note: `hg status some-name` creates a patternmatcher which is not supported
822 825 yet by the Rust implementation of status, but includematcher is supported.
823 826 --include is used below for that reason
824 827
825 828 Remove a directory that contains tracked files
826 829
827 830 $ rm -r subdir
828 831 $ hg status --include subdir
829 832 R subdir/removed
830 833 ! subdir/clean
831 834 ! subdir/deleted
832 835 ! subdir/modified
833 836
834 837 and replace it by a file
835 838
836 839 $ touch subdir
837 840 $ hg status --include subdir
838 841 R subdir/removed
839 842 ! subdir/clean
840 843 ! subdir/deleted
841 844 ! subdir/modified
842 845 ? subdir
843 846
844 847 Replaced a deleted or removed file with a directory
845 848
846 849 $ mkdir deleted removed
847 850 $ touch deleted/1 removed/1
848 851 $ hg status --include deleted --include removed
849 852 R removed
850 853 ! deleted
851 854 ? deleted/1
852 855 ? removed/1
853 856 $ hg add removed/1
854 857 $ hg status --include deleted --include removed
855 858 A removed/1
856 859 R removed
857 860 ! deleted
858 861 ? deleted/1
859 862
860 863 Deeply nested files in an ignored directory are still listed on request
861 864
862 865 $ echo ignored-dir >> .hgignore
863 866 $ mkdir ignored-dir
864 867 $ mkdir ignored-dir/subdir
865 868 $ touch ignored-dir/subdir/1
866 869 $ hg status --ignored
867 870 I ignored
868 871 I ignored-dir/subdir/1
General Comments 0
You need to be logged in to leave comments. Login now