use std::{ collections::HashMap, path::{Path, PathBuf}, sync::atomic::{AtomicUsize, Ordering}, }; use anyhow::Result; use gitbutler_core::projects::ProjectId; use gitbutler_core::{ deltas::{self, operations::Operation}, reader, sessions, virtual_branches::{self, branch, VirtualBranchesHandle}, }; use once_cell::sync::Lazy; use self::branch::BranchId; use crate::handler::support::Fixture; use gitbutler_testsupport::{commit_all, Case}; static TEST_TARGET_INDEX: Lazy = Lazy::new(|| AtomicUsize::new(0)); #[derive(Clone)] pub struct State { inner: gitbutler_watcher::Handler, } impl State { pub(super) fn from_fixture(fixture: &mut Fixture) -> Self { Self { inner: fixture.new_handler(), } } pub(super) fn calculate_delta( &self, path: impl Into, project_id: ProjectId, ) -> Result<()> { self.inner.calculate_deltas(vec![path.into()], project_id)?; Ok(()) } } fn new_test_target() -> virtual_branches::target::Target { virtual_branches::target::Target { branch: format!( "refs/remotes/remote name {}/branch name {}", TEST_TARGET_INDEX.load(Ordering::Relaxed), TEST_TARGET_INDEX.load(Ordering::Relaxed) ) .parse() .unwrap(), remote_url: format!("remote url {}", TEST_TARGET_INDEX.load(Ordering::Relaxed)), sha: format!( "0123456789abcdef0123456789abcdef0123456{}", TEST_TARGET_INDEX.load(Ordering::Relaxed) ) .parse() .unwrap(), } } static TEST_INDEX: Lazy = Lazy::new(|| AtomicUsize::new(0)); fn new_test_branch() -> branch::Branch { TEST_INDEX.fetch_add(1, Ordering::Relaxed); branch::Branch { id: BranchId::generate(), name: format!("branch_name_{}", TEST_INDEX.load(Ordering::Relaxed)), notes: format!("branch_notes_{}", TEST_INDEX.load(Ordering::Relaxed)), applied: true, upstream: Some( format!( "refs/remotes/origin/upstream_{}", TEST_INDEX.load(Ordering::Relaxed) ) .parse() .unwrap(), ), upstream_head: None, created_timestamp_ms: TEST_INDEX.load(Ordering::Relaxed) as u128, updated_timestamp_ms: (TEST_INDEX.load(Ordering::Relaxed) + 100) as u128, head: format!( "0123456789abcdef0123456789abcdef0123456{}", TEST_INDEX.load(Ordering::Relaxed) ) .parse() .unwrap(), tree: format!( "0123456789abcdef0123456789abcdef012345{}", TEST_INDEX.load(Ordering::Relaxed) + 10 ) .parse() .unwrap(), ownership: branch::BranchOwnershipClaims::default(), order: TEST_INDEX.load(Ordering::Relaxed), selected_for_changes: None, } } #[test] fn register_existing_commited_file() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, .. } = &fixture.new_case_with_files(HashMap::from([(PathBuf::from("test.txt"), "test")])); std::fs::write(project.path.join("test.txt"), "test2")?; listener.calculate_delta("test.txt", project.id)?; let session = gb_repository.get_current_session()?.unwrap(); let session_reader = sessions::Reader::open(gb_repository, &session)?; let deltas_reader = deltas::Reader::new(&session_reader); let deltas = deltas_reader.read_file("test.txt")?.unwrap(); assert_eq!(deltas.len(), 1); assert_eq!(deltas[0].operations.len(), 1); assert_eq!( deltas[0].operations[0], Operation::Insert((4, "2".to_string())), ); assert_eq!( std::fs::read_to_string(gb_repository.session_wd_path().join("test.txt"))?, "test2" ); Ok(()) } #[test] fn register_must_init_current_session() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, .. } = &fixture.new_case(); std::fs::write(project.path.join("test.txt"), "test")?; listener.calculate_delta("test.txt", project.id)?; assert!(gb_repository.get_current_session()?.is_some()); Ok(()) } #[test] fn register_must_not_override_current_session() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, .. } = &fixture.new_case(); std::fs::write(project.path.join("test.txt"), "test")?; listener.calculate_delta("test.txt", project.id)?; let session1 = gb_repository.get_current_session()?.unwrap(); std::fs::write(project.path.join("test.txt"), "test2")?; listener.calculate_delta("test.txt", project.id)?; let session2 = gb_repository.get_current_session()?.unwrap(); assert_eq!(session1.id, session2.id); Ok(()) } #[test] fn register_binfile() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, .. } = &fixture.new_case(); std::fs::write( project.path.join("test.bin"), [0, 159, 146, 150, 159, 146, 150], )?; listener.calculate_delta("test.bin", project.id)?; let session = gb_repository.get_current_session()?.unwrap(); let session_reader = sessions::Reader::open(gb_repository, &session)?; let deltas_reader = deltas::Reader::new(&session_reader); let deltas = deltas_reader.read_file("test.bin")?.unwrap(); assert_eq!(deltas.len(), 1); assert_eq!(deltas[0].operations.len(), 0); assert_eq!( std::fs::read_to_string(gb_repository.session_wd_path().join("test.bin"))?, "" ); Ok(()) } #[test] fn register_empty_new_file() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, .. } = &fixture.new_case(); std::fs::write(project.path.join("test.txt"), "")?; listener.calculate_delta("test.txt", project.id)?; let session = gb_repository.get_current_session()?.unwrap(); let session_reader = sessions::Reader::open(gb_repository, &session)?; let deltas_reader = deltas::Reader::new(&session_reader); let deltas = deltas_reader.read_file("test.txt")?.unwrap(); assert_eq!(deltas.len(), 1); assert_eq!(deltas[0].operations.len(), 0); assert_eq!( std::fs::read_to_string(gb_repository.session_wd_path().join("test.txt"))?, "" ); Ok(()) } #[test] fn register_new_file() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, .. } = &fixture.new_case(); std::fs::write(project.path.join("test.txt"), "test")?; listener.calculate_delta("test.txt", project.id)?; let session = gb_repository.get_current_session()?.unwrap(); let session_reader = sessions::Reader::open(gb_repository, &session)?; let deltas_reader = deltas::Reader::new(&session_reader); let deltas = deltas_reader.read_file("test.txt")?.unwrap(); assert_eq!(deltas.len(), 1); assert_eq!(deltas[0].operations.len(), 1); assert_eq!( deltas[0].operations[0], Operation::Insert((0, "test".to_string())), ); assert_eq!( std::fs::read_to_string(gb_repository.session_wd_path().join("test.txt"))?, "test" ); Ok(()) } #[test] fn register_no_changes_saved_thgoughout_flushes() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project_repository, project, .. } = &fixture.new_case(); // file change, wd and deltas are written std::fs::write(project.path.join("test.txt"), "test")?; listener.calculate_delta("test.txt", project.id)?; // make two more sessions. gb_repository.flush(project_repository, None)?; gb_repository.get_or_create_current_session()?; gb_repository.flush(project_repository, None)?; // after some sessions, files from the first change are still there. let session = gb_repository.get_or_create_current_session()?; let session_reader = sessions::Reader::open(gb_repository, &session)?; let files = session_reader.files(None)?; assert_eq!(files.len(), 1); Ok(()) } #[test] fn register_new_file_twice() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, .. } = &fixture.new_case(); std::fs::write(project.path.join("test.txt"), "test")?; listener.calculate_delta("test.txt", project.id)?; let session = gb_repository.get_current_session()?.unwrap(); let session_reader = sessions::Reader::open(gb_repository, &session)?; let deltas_reader = deltas::Reader::new(&session_reader); let deltas = deltas_reader.read_file("test.txt")?.unwrap(); assert_eq!(deltas.len(), 1); assert_eq!(deltas[0].operations.len(), 1); assert_eq!( deltas[0].operations[0], Operation::Insert((0, "test".to_string())), ); assert_eq!( std::fs::read_to_string(gb_repository.session_wd_path().join("test.txt"))?, "test" ); std::fs::write(project.path.join("test.txt"), "test2")?; listener.calculate_delta("test.txt", project.id)?; let deltas = deltas_reader.read_file("test.txt")?.unwrap(); assert_eq!(deltas.len(), 2); assert_eq!(deltas[0].operations.len(), 1); assert_eq!( deltas[0].operations[0], Operation::Insert((0, "test".to_string())), ); assert_eq!(deltas[1].operations.len(), 1); assert_eq!( deltas[1].operations[0], Operation::Insert((4, "2".to_string())), ); assert_eq!( std::fs::read_to_string(gb_repository.session_wd_path().join("test.txt"))?, "test2" ); Ok(()) } #[test] fn register_file_deleted() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project_repository, project, .. } = &fixture.new_case(); { // write file std::fs::write(project.path.join("test.txt"), "test")?; listener.calculate_delta("test.txt", project.id)?; } { // current session must have the deltas, but not the file (it didn't exist) let session = gb_repository.get_current_session()?.unwrap(); let session_reader = sessions::Reader::open(gb_repository, &session)?; let deltas_reader = deltas::Reader::new(&session_reader); let deltas = deltas_reader.read_file("test.txt")?.unwrap(); assert_eq!(deltas.len(), 1); assert_eq!(deltas[0].operations.len(), 1); assert_eq!( deltas[0].operations[0], Operation::Insert((0, "test".to_string())), ); assert_eq!( std::fs::read_to_string(gb_repository.session_wd_path().join("test.txt"))?, "test" ); let files = session_reader.files(None).unwrap(); assert!(files.is_empty()); } gb_repository.flush(project_repository, None)?; { // file should be available in the next session, but not deltas just yet. let session = gb_repository.get_or_create_current_session()?; let session_reader = sessions::Reader::open(gb_repository, &session)?; let files = session_reader.files(None).unwrap(); assert_eq!(files.len(), 1); assert_eq!( files[Path::new("test.txt")], reader::Content::UTF8("test".to_string()) ); let deltas_reader = deltas::Reader::new(&session_reader); let deltas = deltas_reader.read(None)?; assert!(deltas.is_empty()); // removing the file std::fs::remove_file(project.path.join("test.txt"))?; listener.calculate_delta("test.txt", project.id)?; // deltas are recorded let deltas = deltas_reader.read_file("test.txt")?.unwrap(); assert_eq!(deltas.len(), 1); assert_eq!(deltas[0].operations.len(), 1); assert_eq!(deltas[0].operations[0], Operation::Delete((0, 4)),); } gb_repository.flush(project_repository, None)?; { // since file was deleted in the previous session, it should not exist in the new one. let session = gb_repository.get_or_create_current_session()?; let session_reader = sessions::Reader::open(gb_repository, &session)?; let files = session_reader.files(None).unwrap(); assert!(files.is_empty()); } Ok(()) } #[test] fn flow_with_commits() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, project_repository, .. } = &fixture.new_case(); let size = 10; let relative_file_path = Path::new("one/two/test.txt"); for i in 1..=size { std::fs::create_dir_all(Path::new(&project.path).join("one/two"))?; // create a session with a single file change and flush it std::fs::write( Path::new(&project.path).join(relative_file_path), i.to_string(), )?; commit_all(&project_repository.git_repository); listener.calculate_delta(relative_file_path, project.id)?; assert!(gb_repository.flush(project_repository, None)?.is_some()); } // get all the created sessions let mut sessions: Vec = gb_repository .get_sessions_iterator()? .map(Result::unwrap) .collect(); assert_eq!(sessions.len(), size); // verify sessions order is correct let mut last_start = sessions[0].meta.start_timestamp_ms; let mut last_end = sessions[0].meta.start_timestamp_ms; sessions[1..].iter().for_each(|session| { assert!(session.meta.start_timestamp_ms < last_start); assert!(session.meta.last_timestamp_ms < last_end); last_start = session.meta.start_timestamp_ms; last_end = session.meta.last_timestamp_ms; }); sessions.reverse(); // try to reconstruct file state from operations for every session slice for i in 0..sessions.len() { let sessions_slice = &mut sessions[i..]; // collect all operations from sessions in the reverse order let mut operations: Vec = vec![]; for session in &mut *sessions_slice { let session_reader = sessions::Reader::open(gb_repository, session).unwrap(); let deltas_reader = deltas::Reader::new(&session_reader); let deltas_by_filepath = deltas_reader.read(None).unwrap(); for deltas in deltas_by_filepath.values() { for delta in deltas { delta.operations.iter().for_each(|operation| { operations.push(operation.clone()); }); } } } let reader = sessions::Reader::open(gb_repository, sessions_slice.first().unwrap()).unwrap(); let files = reader.files(None).unwrap(); if i == 0 { assert_eq!(files.len(), 0); } else { assert_eq!(files.len(), 1); } let base_file = files.get(&relative_file_path.to_path_buf()); let mut text: Vec = match base_file { Some(reader::Content::UTF8(file)) => file.chars().collect(), _ => vec![], }; for operation in operations { operation.apply(&mut text).unwrap(); } assert_eq!(text.iter().collect::(), size.to_string()); } Ok(()) } #[test] fn flow_no_commits() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, project_repository, .. } = &fixture.new_case(); let size = 10; let relative_file_path = Path::new("one/two/test.txt"); for i in 1..=size { std::fs::create_dir_all(Path::new(&project.path).join("one/two"))?; // create a session with a single file change and flush it std::fs::write( Path::new(&project.path).join(relative_file_path), i.to_string(), )?; listener.calculate_delta(relative_file_path, project.id)?; assert!(gb_repository.flush(project_repository, None)?.is_some()); } // get all the created sessions let mut sessions: Vec = gb_repository .get_sessions_iterator()? .map(Result::unwrap) .collect(); assert_eq!(sessions.len(), size); // verify sessions order is correct let mut last_start = sessions[0].meta.start_timestamp_ms; let mut last_end = sessions[0].meta.start_timestamp_ms; sessions[1..].iter().for_each(|session| { assert!(session.meta.start_timestamp_ms < last_start); assert!(session.meta.last_timestamp_ms < last_end); last_start = session.meta.start_timestamp_ms; last_end = session.meta.last_timestamp_ms; }); sessions.reverse(); // try to reconstruct file state from operations for every session slice for i in 0..sessions.len() { let sessions_slice = &mut sessions[i..]; // collect all operations from sessions in the reverse order let mut operations: Vec = vec![]; for session in &mut *sessions_slice { let session_reader = sessions::Reader::open(gb_repository, session).unwrap(); let deltas_reader = deltas::Reader::new(&session_reader); let deltas_by_filepath = deltas_reader.read(None).unwrap(); for deltas in deltas_by_filepath.values() { for delta in deltas { delta.operations.iter().for_each(|operation| { operations.push(operation.clone()); }); } } } let reader = sessions::Reader::open(gb_repository, sessions_slice.first().unwrap()).unwrap(); let files = reader.files(None).unwrap(); if i == 0 { assert_eq!(files.len(), 0); } else { assert_eq!(files.len(), 1); } let base_file = files.get(&relative_file_path.to_path_buf()); let mut text: Vec = match base_file { Some(reader::Content::UTF8(file)) => file.chars().collect(), _ => vec![], }; for operation in operations { operation.apply(&mut text).unwrap(); } assert_eq!(text.iter().collect::(), size.to_string()); } Ok(()) } #[test] fn flow_signle_session() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, .. } = &fixture.new_case(); let size = 10_i32; let relative_file_path = Path::new("one/two/test.txt"); for i in 1_i32..=size { std::fs::create_dir_all(Path::new(&project.path).join("one/two"))?; // create a session with a single file change and flush it std::fs::write( Path::new(&project.path).join(relative_file_path), i.to_string(), )?; listener.calculate_delta(relative_file_path, project.id)?; } // collect all operations from sessions in the reverse order let mut operations: Vec = vec![]; let session = gb_repository.get_current_session()?.unwrap(); let session_reader = sessions::Reader::open(gb_repository, &session).unwrap(); let deltas_reader = deltas::Reader::new(&session_reader); let deltas_by_filepath = deltas_reader.read(None).unwrap(); for deltas in deltas_by_filepath.values() { for delta in deltas { delta.operations.iter().for_each(|operation| { operations.push(operation.clone()); }); } } let reader = sessions::Reader::open(gb_repository, &session).unwrap(); let files = reader.files(None).unwrap(); let base_file = files.get(&relative_file_path.to_path_buf()); let mut text: Vec = match base_file { Some(reader::Content::UTF8(file)) => file.chars().collect(), _ => vec![], }; for operation in operations { operation.apply(&mut text).unwrap(); } assert_eq!(text.iter().collect::(), size.to_string()); Ok(()) } #[test] fn should_persist_branches_targets_state_between_sessions() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { project, project_repository, .. } = &fixture.new_case_with_files(HashMap::from([(PathBuf::from("test.txt"), "hello world")])); let vb_state = VirtualBranchesHandle::new(&project.gb_dir()); let default_target = new_test_target(); vb_state.set_default_target(default_target.clone())?; let vbranch0 = new_test_branch(); vb_state.set_branch(vbranch0.clone())?; let vbranch1 = new_test_branch(); let vbranch1_target = new_test_target(); vb_state.set_branch(vbranch1.clone())?; vb_state.set_branch_target(vbranch1.id, vbranch1_target.clone())?; std::fs::write(project.path.join("test.txt"), "hello world!").unwrap(); listener.calculate_delta("test.txt", project.id)?; let vb_state = VirtualBranchesHandle::new(&project_repository.project().gb_dir()); let branches = vb_state.list_branches().unwrap(); assert_eq!(branches.len(), 2); let branch_ids = branches.iter().map(|b| b.id).collect::>(); assert!(branch_ids.contains(&vbranch0.id)); assert!(branch_ids.contains(&vbranch1.id)); assert_eq!(vb_state.get_default_target().unwrap(), default_target); assert_eq!( vb_state.get_branch_target(&vbranch0.id).unwrap(), default_target ); assert_eq!( vb_state.get_branch_target(&vbranch1.id).unwrap(), vbranch1_target ); Ok(()) } #[test] fn should_restore_branches_targets_state_from_head_session() -> Result<()> { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { project, .. } = &fixture.new_case_with_files(HashMap::from([(PathBuf::from("test.txt"), "hello world")])); let vb_state = VirtualBranchesHandle::new(&project.gb_dir()); let default_target = new_test_target(); vb_state.set_default_target(default_target.clone())?; let vbranch0 = new_test_branch(); vb_state.set_branch(vbranch0.clone())?; let vbranch1 = new_test_branch(); let vbranch1_target = new_test_target(); vb_state.set_branch(vbranch1.clone())?; vb_state.set_branch_target(vbranch1.id, vbranch1_target.clone())?; std::fs::write(project.path.join("test.txt"), "hello world!").unwrap(); listener.calculate_delta("test.txt", project.id).unwrap(); let branches = vb_state.list_branches().unwrap(); assert_eq!(branches.len(), 2); let branch_ids = branches.iter().map(|b| b.id).collect::>(); assert!(branch_ids.contains(&vbranch0.id)); assert!(branch_ids.contains(&vbranch1.id)); assert_eq!(vb_state.get_default_target().unwrap(), default_target); assert_eq!( vb_state.get_branch_target(&vbranch0.id).unwrap(), default_target ); assert_eq!( vb_state.get_branch_target(&vbranch1.id).unwrap(), vbranch1_target ); Ok(()) } mod flush_wd { use super::*; #[test] fn should_add_new_files_to_session_wd() { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, project_repository, .. } = &fixture.new_case(); // write a file into session std::fs::write(project.path.join("test.txt"), "hello world!").unwrap(); listener.calculate_delta("test.txt", project.id).unwrap(); let flushed_session = gb_repository .flush(project_repository, None) .unwrap() .unwrap(); { // after flush it should be flushed into the commit let session_commit = gb_repository .git_repository() .find_commit(flushed_session.hash.unwrap()) .unwrap(); let commit_reader = reader::Reader::from_commit(gb_repository.git_repository(), &session_commit) .unwrap(); assert_eq!( commit_reader.list_files(Path::new("wd")).unwrap(), vec![Path::new("test.txt")] ); assert_eq!( commit_reader.read(Path::new("wd/test.txt")).unwrap(), reader::Content::UTF8("hello world!".to_string()) ); } // write another file into session std::fs::create_dir_all(project.path.join("one/two")).unwrap(); std::fs::write(project.path.join("one/two/test2.txt"), "hello world!").unwrap(); listener .calculate_delta("one/two/test2.txt", project.id) .unwrap(); let flushed_session = gb_repository .flush(project_repository, None) .unwrap() .unwrap(); { // after flush, it should be flushed into the commit next to the previous one let session_commit = gb_repository .git_repository() .find_commit(flushed_session.hash.unwrap()) .unwrap(); let commit_reader = reader::Reader::from_commit(gb_repository.git_repository(), &session_commit) .unwrap(); assert_eq!( commit_reader.list_files(Path::new("wd")).unwrap(), vec![Path::new("one/two/test2.txt"), Path::new("test.txt"),] ); assert_eq!( commit_reader.read(Path::new("wd/test.txt")).unwrap(), reader::Content::UTF8("hello world!".to_string()) ); assert_eq!( commit_reader .read(Path::new("wd/one/two/test2.txt")) .unwrap(), reader::Content::UTF8("hello world!".to_string()) ); } } #[test] fn should_remove_deleted_files_from_session_wd() { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, project_repository, .. } = &fixture.new_case(); // write a file into session std::fs::write(project.path.join("test.txt"), "hello world!").unwrap(); listener.calculate_delta("test.txt", project.id).unwrap(); std::fs::create_dir_all(project.path.join("one/two")).unwrap(); std::fs::write(project.path.join("one/two/test2.txt"), "hello world!").unwrap(); listener .calculate_delta("one/two/test2.txt", project.id) .unwrap(); let flushed_session = gb_repository .flush(project_repository, None) .unwrap() .unwrap(); { // after flush it should be flushed into the commit let session_commit = gb_repository .git_repository() .find_commit(flushed_session.hash.unwrap()) .unwrap(); let commit_reader = reader::Reader::from_commit(gb_repository.git_repository(), &session_commit) .unwrap(); assert_eq!( commit_reader.list_files(Path::new("wd")).unwrap(), vec![Path::new("one/two/test2.txt"), Path::new("test.txt"),] ); assert_eq!( commit_reader.read(Path::new("wd/test.txt")).unwrap(), reader::Content::UTF8("hello world!".to_string()) ); assert_eq!( commit_reader .read(Path::new("wd/one/two/test2.txt")) .unwrap(), reader::Content::UTF8("hello world!".to_string()) ); } // rm the files std::fs::remove_file(project.path.join("test.txt")).unwrap(); listener.calculate_delta("test.txt", project.id).unwrap(); std::fs::remove_file(project.path.join("one/two/test2.txt")).unwrap(); listener .calculate_delta("one/two/test2.txt", project.id) .unwrap(); let flushed_session = gb_repository .flush(project_repository, None) .unwrap() .unwrap(); { // after flush it should be removed from the commit let session_commit = gb_repository .git_repository() .find_commit(flushed_session.hash.unwrap()) .unwrap(); let commit_reader = reader::Reader::from_commit(gb_repository.git_repository(), &session_commit) .unwrap(); assert!(commit_reader .list_files(Path::new("wd")) .unwrap() .is_empty()); } } #[test] fn should_update_updated_files_in_session_wd() { let mut fixture = Fixture::default(); let listener = State::from_fixture(&mut fixture); let Case { gb_repository, project, project_repository, .. } = &fixture.new_case(); // write a file into session std::fs::write(project.path.join("test.txt"), "hello world!").unwrap(); listener.calculate_delta("test.txt", project.id).unwrap(); std::fs::create_dir_all(project.path.join("one/two")).unwrap(); std::fs::write(project.path.join("one/two/test2.txt"), "hello world!").unwrap(); listener .calculate_delta("one/two/test2.txt", project.id) .unwrap(); let flushed_session = gb_repository .flush(project_repository, None) .unwrap() .unwrap(); { // after flush it should be flushed into the commit let session_commit = gb_repository .git_repository() .find_commit(flushed_session.hash.unwrap()) .unwrap(); let commit_reader = reader::Reader::from_commit(gb_repository.git_repository(), &session_commit) .unwrap(); assert_eq!( commit_reader.list_files(Path::new("wd")).unwrap(), vec![Path::new("one/two/test2.txt"), Path::new("test.txt"),] ); assert_eq!( commit_reader.read(Path::new("wd/test.txt")).unwrap(), reader::Content::UTF8("hello world!".to_string()) ); assert_eq!( commit_reader .read(Path::new("wd/one/two/test2.txt")) .unwrap(), reader::Content::UTF8("hello world!".to_string()) ); } // update the file std::fs::write(project.path.join("test.txt"), "hello world!2").unwrap(); listener.calculate_delta("test.txt", project.id).unwrap(); std::fs::write(project.path.join("one/two/test2.txt"), "hello world!2").unwrap(); listener .calculate_delta("one/two/test2.txt", project.id) .unwrap(); let flushed_session = gb_repository .flush(project_repository, None) .unwrap() .unwrap(); { // after flush it should be updated in the commit let session_commit = gb_repository .git_repository() .find_commit(flushed_session.hash.unwrap()) .unwrap(); let commit_reader = reader::Reader::from_commit(gb_repository.git_repository(), &session_commit) .unwrap(); assert_eq!( commit_reader.list_files(Path::new("wd")).unwrap(), vec![Path::new("one/two/test2.txt"), Path::new("test.txt"),] ); assert_eq!( commit_reader.read(Path::new("wd/test.txt")).unwrap(), reader::Content::UTF8("hello world!2".to_string()) ); assert_eq!( commit_reader .read(Path::new("wd/one/two/test2.txt")) .unwrap(), reader::Content::UTF8("hello world!2".to_string()) ); } } }