Mercurial > public > mercurial-scm > hg-stable
comparison rust/hg-core/src/operations/annotate.rs @ 52974:874c64e041b5
rhg-annotate: support whitespace options
This adds support to rhg annotate for all the whitespace options:
-w, --ignore-all-space
-b, --ignore-space-change
-B, --ignore-blank-lines
-Z, --ignore-space-at-eol
Note that --ignore-blank-lines has no effect on annotate so it is ignored. You
can see this in dagop.py _annotepair which only checks if blocks are '=' or not,
whereas the effect of --ignore-blank-lines is to change some '!' into '~'.
When the other 3 are combined, we use the strongest option since -w implies -b
and -b implies -Z. This is not explicit in the Python implementation, but I have
verified that's how it behaves.
author | Mitchell Kember <mkember@janestreet.com> |
---|---|
date | Fri, 07 Feb 2025 17:42:43 -0500 |
parents | 7b4548a075ab |
children |
comparison
equal
deleted
inserted
replaced
52973:515196315b82 | 52974:874c64e041b5 |
---|---|
1 use std::borrow::Cow; | |
2 | |
1 use crate::{ | 3 use crate::{ |
2 bdiff::{self, Lines}, | 4 bdiff::{self, Lines}, |
3 errors::HgError, | 5 errors::HgError, |
4 repo::Repo, | 6 repo::Repo, |
5 revlog::{ | 7 revlog::{ |
8 manifest::Manifestlog, | 10 manifest::Manifestlog, |
9 }, | 11 }, |
10 utils::{ | 12 utils::{ |
11 self, | 13 self, |
12 hg_path::{HgPath, HgPathBuf}, | 14 hg_path::{HgPath, HgPathBuf}, |
15 strings::{clean_whitespace, CleanWhitespace}, | |
13 }, | 16 }, |
14 AncestorsIterator, FastHashMap, Graph, GraphError, Node, Revision, | 17 AncestorsIterator, FastHashMap, Graph, GraphError, Node, Revision, |
15 NULL_REVISION, | 18 NULL_REVISION, |
16 }; | 19 }; |
17 use itertools::Itertools as _; | 20 use itertools::Itertools as _; |
21 /// Options for [`annotate`]. | 24 /// Options for [`annotate`]. |
22 #[derive(Copy, Clone)] | 25 #[derive(Copy, Clone)] |
23 pub struct AnnotateOptions { | 26 pub struct AnnotateOptions { |
24 pub treat_binary_as_text: bool, | 27 pub treat_binary_as_text: bool, |
25 pub follow_copies: bool, | 28 pub follow_copies: bool, |
29 pub whitespace: CleanWhitespace, | |
26 } | 30 } |
27 | 31 |
28 /// The final result of annotating a file. | 32 /// The final result of annotating a file. |
29 pub enum AnnotateOutput { | 33 pub enum AnnotateOutput { |
30 /// An annotated text file. | 34 /// An annotated text file. |
53 /// The one-based line number in the original file. | 57 /// The one-based line number in the original file. |
54 pub line_number: u32, | 58 pub line_number: u32, |
55 } | 59 } |
56 | 60 |
57 self_cell!( | 61 self_cell!( |
58 /// A wrapper around [`Lines`] that owns the file text. | 62 /// A wrapper around [`Lines`] that owns the buffer the lines point into. |
63 /// The buffer contains the file text processed by [`clean_whitespace`]. | |
59 struct OwnedLines { | 64 struct OwnedLines { |
60 owner: Vec<u8>, | 65 owner: Vec<u8>, |
61 #[covariant] | 66 #[covariant] |
62 dependent: Lines, | 67 dependent: Lines, |
63 } | 68 } |
64 ); | 69 ); |
65 | 70 |
66 impl OwnedLines { | 71 impl OwnedLines { |
67 fn split(data: Vec<u8>) -> Result<Self, HgError> { | 72 /// Cleans `data` based on `whitespace` and then splits into lines. |
73 fn split( | |
74 data: Vec<u8>, | |
75 whitespace: CleanWhitespace, | |
76 ) -> Result<Self, HgError> { | |
77 let data = match clean_whitespace(&data, whitespace) { | |
78 Cow::Borrowed(_) => data, | |
79 Cow::Owned(data) => data, | |
80 }; | |
68 Self::try_new(data, |data| bdiff::split_lines(data)) | 81 Self::try_new(data, |data| bdiff::split_lines(data)) |
69 } | 82 } |
70 | 83 |
71 fn get(&self) -> &Lines { | 84 fn get(&self) -> &Lines { |
72 self.borrow_dependent() | 85 self.borrow_dependent() |
291 } | 304 } |
292 let (parents, file_data) = | 305 let (parents, file_data) = |
293 fls.parents(repo, id, options.follow_copies)?; | 306 fls.parents(repo, id, options.follow_copies)?; |
294 info.parents = Some(parents.clone()); | 307 info.parents = Some(parents.clone()); |
295 if let Some(data) = file_data { | 308 if let Some(data) = file_data { |
296 info.file = AnnotatedFileState::Read(OwnedLines::split(data)?); | 309 info.file = AnnotatedFileState::Read(OwnedLines::split( |
310 data, | |
311 options.whitespace, | |
312 )?); | |
297 } | 313 } |
298 for id in parents { | 314 for id in parents { |
299 let info = graph.get_or_insert_default(id); | 315 let info = graph.get_or_insert_default(id); |
300 info.needed += 1; | 316 info.needed += 1; |
301 if info.parents.is_none() { | 317 if info.parents.is_none() { |
302 visit.push(id); | 318 visit.push(id); |
303 } | 319 } |
304 } | 320 } |
305 } | 321 } |
306 | 322 |
307 // Step 3: Read files and split lines in parallel. | 323 // Step 3: Read files and split lines. Do the base file with and without |
308 graph[base_id].file = | 324 // whitespace cleaning. Do the rest of the files in parallel with rayon. |
309 AnnotatedFileState::Read(OwnedLines::split(base_file_data)?); | 325 let base_file_original_lines = match options.whitespace { |
326 CleanWhitespace::None => None, | |
327 _ => Some(OwnedLines::split( | |
328 base_file_data.clone(), | |
329 CleanWhitespace::None, | |
330 )?), | |
331 }; | |
332 graph[base_id].file = AnnotatedFileState::Read(OwnedLines::split( | |
333 base_file_data, | |
334 options.whitespace, | |
335 )?); | |
310 graph.0.par_iter_mut().try_for_each( | 336 graph.0.par_iter_mut().try_for_each( |
311 |(&id, info)| -> Result<(), HgError> { | 337 |(&id, info)| -> Result<(), HgError> { |
312 if let AnnotatedFileState::None = info.file { | 338 if let AnnotatedFileState::None = info.file { |
313 let lines = | 339 info.file = AnnotatedFileState::Read(OwnedLines::split( |
314 OwnedLines::split(fls.read(id)?.into_file_data()?)?; | 340 fls.read(id)?.into_file_data()?, |
315 info.file = AnnotatedFileState::Read(lines); | 341 options.whitespace, |
342 )?); | |
316 } | 343 } |
317 Ok(()) | 344 Ok(()) |
318 }, | 345 }, |
319 )?; | 346 )?; |
320 | 347 |
374 let AnnotatedFileState::Annotated(AnnotatedFile { lines, annotations }) = | 401 let AnnotatedFileState::Annotated(AnnotatedFile { lines, annotations }) = |
375 std::mem::take(&mut base_info.file) | 402 std::mem::take(&mut base_info.file) |
376 else { | 403 else { |
377 panic!("the base file should have been annotated in step 4") | 404 panic!("the base file should have been annotated in step 4") |
378 }; | 405 }; |
406 // Don't use the lines from the graph if they had whitespace cleaned. | |
407 let lines = base_file_original_lines.unwrap_or(lines); | |
379 // Only convert revisions that actually appear in the final output. | 408 // Only convert revisions that actually appear in the final output. |
380 for &Annotation { id, .. } in &annotations { | 409 for &Annotation { id, .. } in &annotations { |
381 graph[id].revision = ChangelogRevisionState::Needed; | 410 graph[id].revision = ChangelogRevisionState::Needed; |
382 } | 411 } |
383 // Use the same object for all ancestor checks, since it internally | 412 // Use the same object for all ancestor checks, since it internally |