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