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