// Copyright 2021 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::collections::HashMap;use std::collections::HashSet; use indexmap::IndexSet;use indoc::indoc;use jj_lib::backend::CommitId;use jj_lib::backend::FileId;use jj_lib::commit::Commit;use jj_lib::fix::FileFixer;use jj_lib::fix::FileToFix;use jj_lib::fix::FixError;use jj_lib::fix::LineRange;use jj_lib::fix::ParallelFileFixer;use jj_lib::fix::RegionsToFormat;use jj_lib::fix::compute_changed_ranges;use jj_lib::fix::fix_files;use jj_lib::fix::get_base_commit_map;use jj_lib::matchers::EverythingMatcher;use jj_lib::merged_tree::MergedTree;use jj_lib::repo::Repo as _;use jj_lib::repo_path::RepoPath;use jj_lib::repo_path::RepoPathBuf;use jj_lib::rewrite::merge_commit_trees;use jj_lib::store::Store;use jj_lib::transaction::Transaction;use pollster::FutureExt as _;use testutils::CommitBuilderExt as _;use testutils::TestRepo;use testutils::TestResult;use testutils::assert_tree_eq;use testutils::create_tree;use testutils::create_tree_with;use testutils::read_file;use testutils::repo_path;use thiserror::Error; type ReplacementKey = (RepoPathBuf, Option<Vec<u8>>, Vec<u8>); #[derive(Clone, Debug)]struct TestFileFixer { replacements: HashMap<ReplacementKey, Vec<u8>>,} impl TestFileFixer { fn new() -> Self { Self { replacements: HashMap::new(), } } fn add_replacement( &mut self, repo_path: &RepoPath, base_content: Option<&[u8]>, old_content: impl AsRef<[u8]>, new_content: impl AsRef<[u8]>, ) { self.replacements.insert( ( repo_path.to_owned(), base_content.map(|c| c.to_vec()), old_content.as_ref().to_vec(), ), new_content.as_ref().to_vec(), ); } fn fix_file(&mut self, store: &Store, file_to_fix: &FileToFix) -> Result<FileId, FixError> { let old_content = read_file(store, &file_to_fix.repo_path, &file_to_fix.file_id); let base_content = file_to_fix .base_file_id .as_ref() .map(|base_file_id| read_file(store, &file_to_fix.repo_path, base_file_id)); let key = ( file_to_fix.repo_path.clone(), base_content, old_content.clone(), ); let Some(new_content) = self.replacements.remove(&key) else { return Err(make_fix_content_error(&format!( indoc! {" Unexpected fix request: path: {} old_content: {:?} "}, file_to_fix.repo_path.as_internal_file_string(), String::from_utf8_lossy(&old_content) ))); }; let new_file_id = store .write_file(&file_to_fix.repo_path, &mut new_content.as_slice()) .block_on()?; Ok(new_file_id) }} impl FileFixer for TestFileFixer { fn fix_files<'a>( &mut self, store: &Store, files_to_fix: &'a HashSet<FileToFix>, ) -> Result<HashMap<&'a FileToFix, FileId>, FixError> { let mut changed_files = HashMap::new(); for file_to_fix in files_to_fix { let new_file_id = self.fix_file(store, file_to_fix)?; changed_files.insert(file_to_fix, new_file_id); } assert!(self.replacements.is_empty()); Ok(changed_files) }} #[derive(Error, Debug)]#[error("Forced failure: {0}")]struct MyFixerError(String); fn make_fix_content_error(message: &str) -> FixError { FixError::FixContent(Box::new(MyFixerError(message.into())))} // Reads the file from store. If the file starts with "fixme", its contents are// changed to uppercase and the new file id is returned. If the file starts with// "error", an error is raised. Otherwise returns None.//// This is used for testing `ParallelFileFixer`.fn fix_file(store: &Store, file_to_fix: &FileToFix) -> Result<Option<FileId>, FixError> { let old_content = read_file(store, &file_to_fix.repo_path, &file_to_fix.file_id); if let Some(rest) = old_content.strip_prefix(b"fixme:") { let new_content = rest.to_ascii_uppercase(); let new_file_id = store .write_file(&file_to_fix.repo_path, &mut new_content.as_slice()) .block_on()?; Ok(Some(new_file_id)) } else if let Some(rest) = old_content.strip_prefix(b"error:") { Err(make_fix_content_error(str::from_utf8(rest).unwrap())) } else { Ok(None) }} fn create_commit(tx: &mut Transaction, parents: Vec<CommitId>, tree: MergedTree) -> CommitId { tx.repo_mut() .new_commit(parents, tree) .write_unwrap() .id() .clone()} fn line_range(first: usize, last: usize) -> LineRange { LineRange::new(first, last)} #[test]fn test_fix_added_and_modified_files() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "unformatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let tree2 = create_tree(repo, &[(path1, "modified")]); let commit_b = create_commit(&mut tx, vec![commit_a.clone()], tree2); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; file_fixer.add_replacement(path1, None, b"unformatted", b"Formatted"); file_fixer.add_replacement(path1, None, b"modified", b"Formatted"); let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert_eq!(summary.rewrites.len(), 2); assert!(summary.rewrites.contains_key(&commit_a)); assert!(summary.rewrites.contains_key(&commit_b)); assert_eq!(summary.num_checked_commits, 2); assert_eq!(summary.num_fixed_commits, 2); let expected_tree = create_tree(repo, &[(path1, "Formatted")]); let new_commit_a = repo .store() .get_commit(summary.rewrites.get(&commit_a).unwrap())?; assert_tree_eq!(new_commit_a.tree(), expected_tree); let new_commit_b = repo .store() .get_commit(summary.rewrites.get(&commit_b).unwrap())?; assert_tree_eq!(new_commit_b.tree(), expected_tree); Ok(())} #[test]fn test_fixer_does_not_change_content() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "Formatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; file_fixer.add_replacement(path1, None, b"Formatted", b"Formatted"); let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert!(summary.rewrites.is_empty()); assert_eq!(summary.num_checked_commits, 1); assert_eq!(summary.num_fixed_commits, 0); Ok(())} #[test]fn test_empty_commit() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let tree1 = create_tree(repo, &[]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert!(summary.rewrites.is_empty()); assert_eq!(summary.num_checked_commits, 1); assert_eq!(summary.num_fixed_commits, 0); Ok(())} #[test]fn test_fix_empty_revset() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let mut file_fixer = TestFileFixer::new(); let summary = fix_files( vec![], &EverythingMatcher, false, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert!(summary.rewrites.is_empty()); Ok(())} #[test]fn test_fixer_fails() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "unformatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; let result = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on(); let error = result.err().unwrap(); assert_eq!( error.to_string(), indoc! {r#" Forced failure: Unexpected fix request: path: file1 old_content: "unformatted" "#} ); Ok(())} #[test]fn test_unchanged_file_is_not_fixed() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "unformatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let tree2 = create_tree(repo, &[(path1, "unformatted")]); let commit_b = create_commit(&mut tx, vec![commit_a.clone()], tree2); let root_commits = vec![commit_b.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert!(summary.rewrites.is_empty()); assert_eq!(summary.num_checked_commits, 1); assert_eq!(summary.num_fixed_commits, 0); Ok(())} #[test]fn test_fix_include_unchanged_files() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path_changed = repo_path("changed.txt"); let path_unchanged = repo_path("unchanged.txt"); let path_new = repo_path("newfile.txt"); // c1: changed.txt, unchanged.txt let tree1 = create_tree( repo, &[(path_changed, "base"), (path_unchanged, "unformatted 2")], ); let c1 = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); // c2: changed.txt, unchanged.txt, newfile.txt let tree2 = create_tree( repo, &[ (path_changed, "unformatted 1"), (path_unchanged, "unformatted 2"), (path_new, "unformatted 3"), ], ); let c2 = create_commit(&mut tx, vec![c1.clone()], tree2); // Run fix on commit 2 with include_unchanged_files = true. let root_commits = vec![c2.clone()]; let mut file_fixer = TestFileFixer::new(); file_fixer.add_replacement( path_changed, Some(b"base"), b"unformatted 1", b"Formatted 1", ); file_fixer.add_replacement( path_unchanged, Some(b"unformatted 2"), b"unformatted 2", b"sorted includes\nunformatted 2", ); file_fixer.add_replacement(path_new, None, b"unformatted 3", b"Formatted 3"); let include_unchanged_files = true; let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert_eq!(summary.rewrites.len(), 1); assert!(summary.rewrites.contains_key(&c2)); let new_c2 = repo .store() .get_commit(summary.rewrites.get(&c2).unwrap())?; let expected_tree = create_tree( repo, &[ (path_changed, "Formatted 1"), (path_unchanged, "sorted includes\nunformatted 2"), (path_new, "Formatted 3"), ], ); assert_tree_eq!(new_c2.tree(), expected_tree); Ok(())} /// If a descendant is already correctly formatted, it should still be rewritten/// but its tree should be preserved.#[test]fn test_already_fixed_descendant() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "unformatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let tree2 = create_tree(repo, &[(path1, "Formatted")]); let commit_b = create_commit(&mut tx, vec![commit_a.clone()], tree2.clone()); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = true; file_fixer.add_replacement(path1, None, b"unformatted", b"Formatted"); file_fixer.add_replacement(path1, None, b"Formatted", b"Formatted"); let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert_eq!(summary.rewrites.len(), 2); assert!(summary.rewrites.contains_key(&commit_a)); assert!(summary.rewrites.contains_key(&commit_b)); assert_eq!(summary.num_checked_commits, 2); assert_eq!(summary.num_fixed_commits, 1); let new_commit_a = repo .store() .get_commit(summary.rewrites.get(&commit_a).unwrap())?; assert_tree_eq!(new_commit_a.tree(), tree2); let new_commit_b = repo .store() .get_commit(summary.rewrites.get(&commit_a).unwrap())?; assert_tree_eq!(new_commit_b.tree(), tree2); Ok(())} #[test]fn test_parallel_fixer_basic() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "fixme:content")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let root_commits = vec![commit_a.clone()]; let include_unchanged_files = false; let mut parallel_fixer = ParallelFileFixer::new(fix_file); let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files,Lines 1–500 of 1434. Load more
// Copyright 2021 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::collections::HashMap; use std::collections::HashSet; use indexmap::IndexSet; use indoc::indoc; use jj_lib::backend::CommitId; use jj_lib::backend::FileId; use jj_lib::commit::Commit; use jj_lib::fix::FileFixer; use jj_lib::fix::FileToFix; use jj_lib::fix::FixError; use jj_lib::fix::LineRange; use jj_lib::fix::ParallelFileFixer; use jj_lib::fix::RegionsToFormat; use jj_lib::fix::compute_changed_ranges; use jj_lib::fix::fix_files; use jj_lib::fix::get_base_commit_map; use jj_lib::matchers::EverythingMatcher; use jj_lib::merged_tree::MergedTree; use jj_lib::repo::Repo as _; use jj_lib::repo_path::RepoPath; use jj_lib::repo_path::RepoPathBuf; use jj_lib::rewrite::merge_commit_trees; use jj_lib::store::Store; use jj_lib::transaction::Transaction; use pollster::FutureExt as _; use testutils::CommitBuilderExt as _; use testutils::TestRepo; use testutils::TestResult; use testutils::assert_tree_eq; use testutils::create_tree; use testutils::create_tree_with; use testutils::read_file; use testutils::repo_path; use thiserror::Error; type ReplacementKey = (RepoPathBuf, Option<Vec<u8>>, Vec<u8>); #[derive(Clone, Debug)] struct TestFileFixer { replacements: HashMap<ReplacementKey, Vec<u8>>, } impl TestFileFixer { fn new() -> Self { Self { replacements: HashMap::new(), } } fn add_replacement( &mut self, repo_path: &RepoPath, base_content: Option<&[u8]>, old_content: impl AsRef<[u8]>, new_content: impl AsRef<[u8]>, ) { self.replacements.insert( ( repo_path.to_owned(), base_content.map(|c| c.to_vec()), old_content.as_ref().to_vec(), ), new_content.as_ref().to_vec(), ); } fn fix_file(&mut self, store: &Store, file_to_fix: &FileToFix) -> Result<FileId, FixError> { let old_content = read_file(store, &file_to_fix.repo_path, &file_to_fix.file_id); let base_content = file_to_fix .base_file_id .as_ref() .map(|base_file_id| read_file(store, &file_to_fix.repo_path, base_file_id)); let key = ( file_to_fix.repo_path.clone(), base_content, old_content.clone(), ); let Some(new_content) = self.replacements.remove(&key) else { return Err(make_fix_content_error(&format!( indoc! {" Unexpected fix request: path: {} old_content: {:?} "}, file_to_fix.repo_path.as_internal_file_string(), String::from_utf8_lossy(&old_content) ))); }; let new_file_id = store .write_file(&file_to_fix.repo_path, &mut new_content.as_slice()) .block_on()?; Ok(new_file_id) } } impl FileFixer for TestFileFixer { fn fix_files<'a>( &mut self, store: &Store, files_to_fix: &'a HashSet<FileToFix>, ) -> Result<HashMap<&'a FileToFix, FileId>, FixError> { let mut changed_files = HashMap::new(); for file_to_fix in files_to_fix { let new_file_id = self.fix_file(store, file_to_fix)?; changed_files.insert(file_to_fix, new_file_id); } assert!(self.replacements.is_empty()); Ok(changed_files) } } #[derive(Error, Debug)] #[error("Forced failure: {0}")] struct MyFixerError(String); fn make_fix_content_error(message: &str) -> FixError { FixError::FixContent(Box::new(MyFixerError(message.into()))) } // Reads the file from store. If the file starts with "fixme", its contents are // changed to uppercase and the new file id is returned. If the file starts with // "error", an error is raised. Otherwise returns None. // // This is used for testing `ParallelFileFixer`. fn fix_file(store: &Store, file_to_fix: &FileToFix) -> Result<Option<FileId>, FixError> { let old_content = read_file(store, &file_to_fix.repo_path, &file_to_fix.file_id); if let Some(rest) = old_content.strip_prefix(b"fixme:") { let new_content = rest.to_ascii_uppercase(); let new_file_id = store .write_file(&file_to_fix.repo_path, &mut new_content.as_slice()) .block_on()?; Ok(Some(new_file_id)) } else if let Some(rest) = old_content.strip_prefix(b"error:") { Err(make_fix_content_error(str::from_utf8(rest).unwrap())) } else { Ok(None) } } fn create_commit(tx: &mut Transaction, parents: Vec<CommitId>, tree: MergedTree) -> CommitId { tx.repo_mut() .new_commit(parents, tree) .write_unwrap() .id() .clone() } fn line_range(first: usize, last: usize) -> LineRange { LineRange::new(first, last) } #[test] fn test_fix_added_and_modified_files() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "unformatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let tree2 = create_tree(repo, &[(path1, "modified")]); let commit_b = create_commit(&mut tx, vec![commit_a.clone()], tree2); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; file_fixer.add_replacement(path1, None, b"unformatted", b"Formatted"); file_fixer.add_replacement(path1, None, b"modified", b"Formatted"); let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert_eq!(summary.rewrites.len(), 2); assert!(summary.rewrites.contains_key(&commit_a)); assert!(summary.rewrites.contains_key(&commit_b)); assert_eq!(summary.num_checked_commits, 2); assert_eq!(summary.num_fixed_commits, 2); let expected_tree = create_tree(repo, &[(path1, "Formatted")]); let new_commit_a = repo .store() .get_commit(summary.rewrites.get(&commit_a).unwrap())?; assert_tree_eq!(new_commit_a.tree(), expected_tree); let new_commit_b = repo .store() .get_commit(summary.rewrites.get(&commit_b).unwrap())?; assert_tree_eq!(new_commit_b.tree(), expected_tree); Ok(()) } #[test] fn test_fixer_does_not_change_content() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "Formatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; file_fixer.add_replacement(path1, None, b"Formatted", b"Formatted"); let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert!(summary.rewrites.is_empty()); assert_eq!(summary.num_checked_commits, 1); assert_eq!(summary.num_fixed_commits, 0); Ok(()) } #[test] fn test_empty_commit() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let tree1 = create_tree(repo, &[]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert!(summary.rewrites.is_empty()); assert_eq!(summary.num_checked_commits, 1); assert_eq!(summary.num_fixed_commits, 0); Ok(()) } #[test] fn test_fix_empty_revset() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let mut file_fixer = TestFileFixer::new(); let summary = fix_files( vec![], &EverythingMatcher, false, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert!(summary.rewrites.is_empty()); Ok(()) } #[test] fn test_fixer_fails() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "unformatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; let result = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on(); let error = result.err().unwrap(); assert_eq!( error.to_string(), indoc! {r#" Forced failure: Unexpected fix request: path: file1 old_content: "unformatted" "#} ); Ok(()) } #[test] fn test_unchanged_file_is_not_fixed() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "unformatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let tree2 = create_tree(repo, &[(path1, "unformatted")]); let commit_b = create_commit(&mut tx, vec![commit_a.clone()], tree2); let root_commits = vec![commit_b.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = false; let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert!(summary.rewrites.is_empty()); assert_eq!(summary.num_checked_commits, 1); assert_eq!(summary.num_fixed_commits, 0); Ok(()) } #[test] fn test_fix_include_unchanged_files() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path_changed = repo_path("changed.txt"); let path_unchanged = repo_path("unchanged.txt"); let path_new = repo_path("newfile.txt"); // c1: changed.txt, unchanged.txt let tree1 = create_tree( repo, &[(path_changed, "base"), (path_unchanged, "unformatted 2")], ); let c1 = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); // c2: changed.txt, unchanged.txt, newfile.txt let tree2 = create_tree( repo, &[ (path_changed, "unformatted 1"), (path_unchanged, "unformatted 2"), (path_new, "unformatted 3"), ], ); let c2 = create_commit(&mut tx, vec![c1.clone()], tree2); // Run fix on commit 2 with include_unchanged_files = true. let root_commits = vec![c2.clone()]; let mut file_fixer = TestFileFixer::new(); file_fixer.add_replacement( path_changed, Some(b"base"), b"unformatted 1", b"Formatted 1", ); file_fixer.add_replacement( path_unchanged, Some(b"unformatted 2"), b"unformatted 2", b"sorted includes\nunformatted 2", ); file_fixer.add_replacement(path_new, None, b"unformatted 3", b"Formatted 3"); let include_unchanged_files = true; let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert_eq!(summary.rewrites.len(), 1); assert!(summary.rewrites.contains_key(&c2)); let new_c2 = repo .store() .get_commit(summary.rewrites.get(&c2).unwrap())?; let expected_tree = create_tree( repo, &[ (path_changed, "Formatted 1"), (path_unchanged, "sorted includes\nunformatted 2"), (path_new, "Formatted 3"), ], ); assert_tree_eq!(new_c2.tree(), expected_tree); Ok(()) } /// If a descendant is already correctly formatted, it should still be rewritten /// but its tree should be preserved. #[test] fn test_already_fixed_descendant() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "unformatted")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let tree2 = create_tree(repo, &[(path1, "Formatted")]); let commit_b = create_commit(&mut tx, vec![commit_a.clone()], tree2.clone()); let root_commits = vec![commit_a.clone()]; let mut file_fixer = TestFileFixer::new(); let include_unchanged_files = true; file_fixer.add_replacement(path1, None, b"unformatted", b"Formatted"); file_fixer.add_replacement(path1, None, b"Formatted", b"Formatted"); let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files, tx.repo_mut(), &mut file_fixer, ) .block_on()?; assert_eq!(summary.rewrites.len(), 2); assert!(summary.rewrites.contains_key(&commit_a)); assert!(summary.rewrites.contains_key(&commit_b)); assert_eq!(summary.num_checked_commits, 2); assert_eq!(summary.num_fixed_commits, 1); let new_commit_a = repo .store() .get_commit(summary.rewrites.get(&commit_a).unwrap())?; assert_tree_eq!(new_commit_a.tree(), tree2); let new_commit_b = repo .store() .get_commit(summary.rewrites.get(&commit_a).unwrap())?; assert_tree_eq!(new_commit_b.tree(), tree2); Ok(()) } #[test] fn test_parallel_fixer_basic() -> TestResult { let test_repo = TestRepo::init(); let repo = &test_repo.repo; let mut tx = repo.start_transaction(); let path1 = repo_path("file1"); let tree1 = create_tree(repo, &[(path1, "fixme:content")]); let commit_a = create_commit(&mut tx, vec![repo.store().root_commit_id().clone()], tree1); let root_commits = vec![commit_a.clone()]; let include_unchanged_files = false; let mut parallel_fixer = ParallelFileFixer::new(fix_file); let summary = fix_files( root_commits, &EverythingMatcher, include_unchanged_files,