From 92b1c00e583b48420c458b998186c2b8c1a225dd Mon Sep 17 00:00:00 2001 From: Nikita Galaiko Date: Wed, 19 Apr 2023 19:53:57 +0200 Subject: [PATCH] fix deltas calcuation & add more tests --- .../watcher/handlers/project_file_change.rs | 55 +++--- .../handlers/project_file_change_tests.rs | 158 +++++++++++++++++- 2 files changed, 179 insertions(+), 34 deletions(-) diff --git a/src-tauri/src/app/watcher/handlers/project_file_change.rs b/src-tauri/src/app/watcher/handlers/project_file_change.rs index 3c8f75d9c..0537db30a 100644 --- a/src-tauri/src/app/watcher/handlers/project_file_change.rs +++ b/src-tauri/src/app/watcher/handlers/project_file_change.rs @@ -88,27 +88,32 @@ impl<'listener> Handler<'listener> { } } - fn get_latest_file_from_previous_session(&self, path: &str) -> Result> { - match self - .gb_repository - .get_sessions_iterator() - .context("failed to get sessions iterator")? - .next() - { - Some(Ok(head_session)) => { - let session_path = std::path::Path::new("wd").join(path); - let head_session_reader = self.gb_repository.get_session_reader(&head_session)?; + fn get_latest_file_contents( + &self, + project_repository: &project_repository::Repository, + path: &std::path::Path, + ) -> Result> { + let path = path.to_str().unwrap(); + match self.gb_repository.git_repository.head() { + Ok(head) => { + let head_commit = head.peel_to_commit()?; + let commit_reader = reader::CommitReader::from_commit( + &self.gb_repository.git_repository, + head_commit, + ) + .context("failed to get commit reader")?; - if !head_session_reader.exists(session_path.to_str().unwrap()) { + let session_path = std::path::Path::new("wd").join(path); + if !commit_reader.exists(session_path.to_str().unwrap()) { return Ok(None); } - if head_session_reader.size(session_path.to_str().unwrap())? > 100_000 { + if commit_reader.size(session_path.to_str().unwrap())? > 100_000 { log::warn!("{}: ignoring large file: {}", self.project_id, path); return Ok(None); } - match head_session_reader.read(session_path.to_str().unwrap())? { + match commit_reader.read(session_path.to_str().unwrap())? { reader::Content::UTF8(content) => Ok(Some(content)), reader::Content::Binary(_) => { log::warn!("{}: ignoring non-utf8 file: {}", self.project_id, path); @@ -116,24 +121,12 @@ impl<'listener> Handler<'listener> { } } } - Some(Err(err)) => Err(err).context("failed to get head session"), - None => Ok(None), - } - } - - fn get_latest_file_contents( - &self, - project_repository: &project_repository::Repository, - path: &std::path::Path, - ) -> Result> { - let path = path.to_str().unwrap(); - - match self - .get_latest_file_from_previous_session(path) - .context("failed to get latest file from previous session")? - { - Some(content) => Ok(Some(content)), - None => self.get_latest_file_from_repository_head(project_repository, path), + Err(err) => { + if err.code() == git2::ErrorCode::UnbornBranch { + return self.get_latest_file_from_repository_head(project_repository, path); + } + Err(err).context("failed to get head")? + } } } diff --git a/src-tauri/src/app/watcher/handlers/project_file_change_tests.rs b/src-tauri/src/app/watcher/handlers/project_file_change_tests.rs index fa4684179..ae24b7078 100644 --- a/src-tauri/src/app/watcher/handlers/project_file_change_tests.rs +++ b/src-tauri/src/app/watcher/handlers/project_file_change_tests.rs @@ -311,7 +311,7 @@ fn test_register_file_delted() -> Result<()> { } #[test] -fn test_flow() -> Result<()> { +fn test_flow_with_commits() -> Result<()> { let repository = test_repository()?; let project = test_project(&repository)?; let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string(); @@ -337,8 +337,9 @@ fn test_flow() -> Result<()> { i.to_string(), )?; + commit_all(&repository)?; listener.handle(&relative_file_path)?; - gb_repo.flush()?; + assert!(gb_repo.flush()?.is_some()); } // get all the created sessions @@ -399,6 +400,157 @@ fn test_flow() -> Result<()> { assert_eq!(text.iter().collect::(), size.to_string()); } - + Ok(()) +} + +#[test] +fn test_flow_no_commits() -> Result<()> { + let repository = test_repository()?; + let project = test_project(&repository)?; + let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string(); + let storage = storage::Storage::from_path(tempdir()?.path().to_path_buf()); + let user_store = users::Storage::new(storage.clone()); + let project_store = projects::Storage::new(storage); + project_store.add_project(&project)?; + let gb_repo = gb_repository::Repository::open( + gb_repo_path, + project.id.clone(), + project_store.clone(), + user_store, + )?; + let listener = Handler::new(project.id.clone(), project_store, &gb_repo); + + let size = 10; + let relative_file_path = std::path::Path::new("one/two/test.txt"); + for i in 1..=size { + std::fs::create_dir_all(std::path::Path::new(&project.path).join("one/two"))?; + // create a session with a single file change and flush it + std::fs::write( + std::path::Path::new(&project.path).join(relative_file_path), + i.to_string(), + )?; + + listener.handle(&relative_file_path)?; + assert!(gb_repo.flush()?.is_some()); + } + + // get all the created sessions + let mut sessions: Vec = gb_repo + .get_sessions_iterator()? + .map(|s| s.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() - 1 { + let sessions_slice = &mut sessions[i..]; + + // collect all operations from sessions in the reverse order + let mut operations: Vec = vec![]; + sessions_slice.iter().for_each(|session| { + let reader = gb_repo.get_session_reader(&session).unwrap(); + let deltas_by_filepath = reader.deltas(None).unwrap(); + for deltas in deltas_by_filepath.values() { + deltas.iter().for_each(|delta| { + delta.operations.iter().for_each(|operation| { + operations.push(operation.clone()); + }); + }); + } + }); + + let reader = gb_repo + .get_session_reader(&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_str().unwrap().to_string()); + let mut text: Vec = match base_file { + Some(file) => file.chars().collect(), + None => vec![], + }; + + for operation in operations { + operation.apply(&mut text).unwrap(); + } + + assert_eq!(text.iter().collect::(), size.to_string()); + } + Ok(()) +} + +#[test] +fn test_flow_signle_session() -> Result<()> { + let repository = test_repository()?; + let project = test_project(&repository)?; + let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string(); + let storage = storage::Storage::from_path(tempdir()?.path().to_path_buf()); + let user_store = users::Storage::new(storage.clone()); + let project_store = projects::Storage::new(storage); + project_store.add_project(&project)?; + let gb_repo = gb_repository::Repository::open( + gb_repo_path, + project.id.clone(), + project_store.clone(), + user_store, + )?; + let listener = Handler::new(project.id.clone(), project_store, &gb_repo); + + let size = 10; + let relative_file_path = std::path::Path::new("one/two/test.txt"); + for i in 1..=size { + std::fs::create_dir_all(std::path::Path::new(&project.path).join("one/two"))?; + // create a session with a single file change and flush it + std::fs::write( + std::path::Path::new(&project.path).join(relative_file_path), + i.to_string(), + )?; + + listener.handle(&relative_file_path)?; + } + + // collect all operations from sessions in the reverse order + let mut operations: Vec = vec![]; + let session = gb_repo.get_current_session()?.unwrap(); + let reader = gb_repo.get_session_reader(&session).unwrap(); + let deltas_by_filepath = reader.deltas(None).unwrap(); + for deltas in deltas_by_filepath.values() { + deltas.iter().for_each(|delta| { + delta.operations.iter().for_each(|operation| { + operations.push(operation.clone()); + }); + }); + } + + let reader = gb_repo.get_session_reader(&session).unwrap(); + let files = reader.files(None).unwrap(); + + let base_file = files.get(&relative_file_path.to_str().unwrap().to_string()); + let mut text: Vec = match base_file { + Some(file) => file.chars().collect(), + None => vec![], + }; + + for operation in operations { + operation.apply(&mut text).unwrap(); + } + + assert_eq!(text.iter().collect::(), size.to_string()); Ok(()) }