From dcc1b373312f6180c8377c6b41caeeeeaef07873 Mon Sep 17 00:00:00 2001 From: Scott Chacon Date: Fri, 17 May 2024 15:11:09 +0200 Subject: [PATCH] rewrite our oplog tests --- crates/gitbutler-core/src/ops/oplog.rs | 186 ----------------- .../tests/suite/virtual_branches/mod.rs | 5 +- .../tests/suite/virtual_branches/oplog.rs | 193 ++++++++++++++++++ 3 files changed, 197 insertions(+), 187 deletions(-) create mode 100644 crates/gitbutler-core/tests/suite/virtual_branches/oplog.rs diff --git a/crates/gitbutler-core/src/ops/oplog.rs b/crates/gitbutler-core/src/ops/oplog.rs index 613d92a2d..b0317473b 100644 --- a/crates/gitbutler-core/src/ops/oplog.rs +++ b/crates/gitbutler-core/src/ops/oplog.rs @@ -351,7 +351,6 @@ impl Oplog for Project { .get_name("workdir") .ok_or(anyhow!("failed to get workdir tree entry"))?; - // make sure we reconstitute any commits that were in the snapshot that are not here for some reason // for every entry in the virtual_branches subtree, reconsitute the commits let vb_tree_entry = top_tree @@ -626,188 +625,3 @@ fn get_exclude_list(repo: &git2::Repository) -> Result { .join(" "); Ok(files_to_exclude) } - -#[cfg(test)] -mod tests { - use std::io::Write; - - use crate::virtual_branches::Branch; - - use super::*; - use tempfile::tempdir; - - #[test] - fn test_create_and_restore() { - let dir = tempdir().unwrap(); - let repo = git2::Repository::init(dir.path()).unwrap(); - let file_path = dir.path().join("1.txt"); - std::fs::write(file_path, "test").unwrap(); - let file_path = dir.path().join("2.txt"); - std::fs::write(file_path, "test").unwrap(); - let mut index = repo.index().unwrap(); - index.add_path(&PathBuf::from("1.txt")).unwrap(); - index.add_path(&PathBuf::from("2.txt")).unwrap(); - let oid = index.write_tree().unwrap(); - let name = "Your Name"; - let email = "your.email@example.com"; - let signature = git2::Signature::now(name, email).unwrap(); - let initial_commit = repo - .commit( - Some("HEAD"), - &signature, - &signature, - "initial commit", - &repo.find_tree(oid).unwrap(), - &[], - ) - .unwrap(); - - // create a new branch called "gitbutler/integraion" from initial commit - repo.branch( - "gitbutler/integration", - &repo.find_commit(initial_commit).unwrap(), - false, - ) - .unwrap(); - - let project = Project { - path: dir.path().to_path_buf(), - enable_snapshots: Some(true), - ..Default::default() - }; - // create gb_dir folder - std::fs::create_dir_all(project.gb_dir()).unwrap(); - - let vb_state = project.virtual_branches(); - - let target_sha = initial_commit.to_string(); - let default_target = crate::virtual_branches::target::Target { - branch: crate::git::RemoteRefname::new("origin", "main"), - remote_url: Default::default(), - sha: crate::git::Oid::from_str(&target_sha).unwrap(), - push_remote_name: None, - }; - vb_state.set_default_target(default_target.clone()).unwrap(); - let file_path = dir.path().join("uncommitted.txt"); - std::fs::write(file_path, "test").unwrap(); - - let file_path = dir.path().join("large.txt"); - // write 33MB of random data in the file - let mut file = std::fs::File::create(file_path).unwrap(); - for _ in 0..33 * 1024 { - let data = [0u8; 1024]; - file.write_all(&data).unwrap(); - } - - // Create conflict state - let conflicts_path = dir.path().join(".git").join("conflicts"); - std::fs::write(&conflicts_path, "conflict A").unwrap(); - let base_merge_parent_path = dir.path().join(".git").join("base_merge_parent"); - std::fs::write(&base_merge_parent_path, "parent A").unwrap(); - - // create a snapshot - project - .create_snapshot(SnapshotDetails::new(OperationType::CreateCommit)) - .unwrap(); - - // The large file is still here but it will not be part of the snapshot - let file_path = dir.path().join("large.txt"); - assert!(file_path.exists()); - - // Modify file 1, remove file 2, create file 3 - let file_path = dir.path().join("1.txt"); - std::fs::write(file_path, "TEST").unwrap(); - let file_path = dir.path().join("2.txt"); - std::fs::remove_file(file_path).unwrap(); - let file_path = dir.path().join("3.txt"); - std::fs::write(file_path, "something_new").unwrap(); - let file_path = dir.path().join("uncommitted.txt"); - std::fs::write(file_path, "TEST").unwrap(); - - // Create a fake branch in virtual_branches.toml - let id = crate::id::Id::from_str("9acb2a3b-cddf-47d7-b531-a7798978c237").unwrap(); - vb_state - .set_branch(Branch { - id, - ..Default::default() - }) - .unwrap(); - assert!(vb_state.get_branch(&id).is_ok()); - - // remove remove conflict files - std::fs::remove_file(&conflicts_path).unwrap(); - std::fs::remove_file(&base_merge_parent_path).unwrap(); - // New snapshot with the conflicts removed - let conflicts_removed_snapshot = project - .create_snapshot(SnapshotDetails::new(OperationType::UpdateWorkspaceBase)) - .unwrap(); - - let initial_snapshot = &project.list_snapshots(10, None).unwrap()[1]; - assert_eq!( - initial_snapshot.files_changed, - vec![ - PathBuf::from_str("1.txt").unwrap(), - PathBuf::from_str("2.txt").unwrap(), - PathBuf::from_str("uncommitted.txt").unwrap() - ] - ); - assert_eq!(initial_snapshot.lines_added, 3); - assert_eq!(initial_snapshot.lines_removed, 0); - let second_snapshot = &project.list_snapshots(10, None).unwrap()[0]; - assert_eq!( - second_snapshot.files_changed, - vec![ - PathBuf::from_str("1.txt").unwrap(), - PathBuf::from_str("2.txt").unwrap(), - PathBuf::from_str("3.txt").unwrap(), - PathBuf::from_str("uncommitted.txt").unwrap() - ] - ); - assert_eq!(second_snapshot.lines_added, 3); - assert_eq!(second_snapshot.lines_removed, 3); - - // restore from the initial snapshot - project - .restore_snapshot(initial_snapshot.id.clone()) - .unwrap(); - - let file_path = dir.path().join("1.txt"); - let file_lines = std::fs::read_to_string(file_path).unwrap(); - assert_eq!(file_lines, "test"); - let file_path = dir.path().join("2.txt"); - assert!(file_path.exists()); - let file_lines = std::fs::read_to_string(file_path).unwrap(); - assert_eq!(file_lines, "test"); - let file_path = dir.path().join("3.txt"); - assert!(!file_path.exists()); - let file_path = dir.path().join("uncommitted.txt"); - let file_lines = std::fs::read_to_string(file_path).unwrap(); - assert_eq!(file_lines, "test"); - - // The large file is still here but it was not be part of the snapshot - let file_path = dir.path().join("large.txt"); - assert!(file_path.exists()); - - // The fake branch is gone - assert!(vb_state.get_branch(&id).is_err()); - - // The conflict files are restored - let file_lines = std::fs::read_to_string(&conflicts_path).unwrap(); - assert_eq!(file_lines, "conflict A"); - let file_lines = std::fs::read_to_string(&base_merge_parent_path).unwrap(); - assert_eq!(file_lines, "parent A"); - - // Restore from the second snapshot - project - .restore_snapshot(conflicts_removed_snapshot.unwrap()) - .unwrap(); - - // The conflicts are not present - assert!(!&conflicts_path.exists()); - assert!(!&base_merge_parent_path.exists()); - } - - // test no oplog.toml found - // test oplog.toml head is not a commit - // test missing commits are recreated -} diff --git a/crates/gitbutler-core/tests/suite/virtual_branches/mod.rs b/crates/gitbutler-core/tests/suite/virtual_branches/mod.rs index 663721cee..2d1bfd833 100644 --- a/crates/gitbutler-core/tests/suite/virtual_branches/mod.rs +++ b/crates/gitbutler-core/tests/suite/virtual_branches/mod.rs @@ -2,7 +2,7 @@ use std::{fs, path, str::FromStr}; use gitbutler_core::{ git, - projects::{self, ProjectId}, + projects::{self, Project, ProjectId}, users, virtual_branches::{branch, errors, Controller}, }; @@ -13,6 +13,7 @@ use gitbutler_testsupport::{paths, TestProject, VAR_NO_CLEANUP}; struct Test { repository: TestProject, project_id: ProjectId, + project: Project, projects: projects::Controller, controller: Controller, data_dir: Option, @@ -43,6 +44,7 @@ impl Default for Test { project_id: project.id, controller: Controller::new(projects.clone(), users, helper), projects, + project, data_dir: Some(data_dir), } } @@ -59,6 +61,7 @@ mod init; mod insert_blank_commit; mod move_commit_file; mod move_commit_to_vbranch; +mod oplog; mod references; mod reorder_commit; mod reset_virtual_branch; diff --git a/crates/gitbutler-core/tests/suite/virtual_branches/oplog.rs b/crates/gitbutler-core/tests/suite/virtual_branches/oplog.rs new file mode 100644 index 000000000..532d214ee --- /dev/null +++ b/crates/gitbutler-core/tests/suite/virtual_branches/oplog.rs @@ -0,0 +1,193 @@ +use std::io::Write; + +use gitbutler_core::ops::oplog::Oplog; + +use super::*; + +#[tokio::test] +async fn test_basic_oplog() { + let Test { + repository, + project_id, + controller, + project, + .. + } = &Test::default(); + + controller + .set_base_branch(project_id, &"refs/remotes/origin/master".parse().unwrap()) + .await + .unwrap(); + + let branch_id = controller + .create_virtual_branch(project_id, &branch::BranchCreateRequest::default()) + .await + .unwrap(); + + // create commit + fs::write(repository.path().join("file.txt"), "content").unwrap(); + let _commit1_id = controller + .create_commit(project_id, &branch_id, "commit one", None, false) + .await + .unwrap(); + + // dont store large files + let file_path = repository.path().join("large.txt"); + // write 33MB of random data in the file + let mut file = std::fs::File::create(file_path).unwrap(); + for _ in 0..33 * 1024 { + let data = [0u8; 1024]; + file.write_all(&data).unwrap(); + } + + // create commit with large file + fs::write(repository.path().join("file2.txt"), "content2").unwrap(); + fs::write(repository.path().join("file3.txt"), "content3").unwrap(); + let commit2_id = controller + .create_commit(project_id, &branch_id, "commit two", None, false) + .await + .unwrap(); + + // Create conflict state + let conflicts_path = repository.path().join(".git").join("conflicts"); + std::fs::write(&conflicts_path, "conflict A").unwrap(); + let base_merge_parent_path = repository.path().join(".git").join("base_merge_parent"); + std::fs::write(&base_merge_parent_path, "parent A").unwrap(); + + // create state with conflict state + let _empty_branch_id = controller + .create_virtual_branch(project_id, &branch::BranchCreateRequest::default()) + .await + .unwrap(); + + std::fs::remove_file(&base_merge_parent_path).unwrap(); + std::fs::remove_file(&conflicts_path).unwrap(); + + fs::write(repository.path().join("file4.txt"), "content4").unwrap(); + let _commit3_id = controller + .create_commit(project_id, &branch_id, "commit three", None, false) + .await + .unwrap(); + + let branch = controller + .list_virtual_branches(project_id) + .await + .unwrap() + .0 + .into_iter() + .find(|b| b.id == branch_id) + .unwrap(); + + let branches = controller.list_virtual_branches(project_id).await.unwrap(); + assert_eq!(branches.0.len(), 2); + + assert_eq!(branch.commits.len(), 3); + assert_eq!(branch.commits[0].files.len(), 1); + assert_eq!(branch.commits[1].files.len(), 3); + + let snapshots = project.list_snapshots(10, None).unwrap(); + + let ops = snapshots + .iter() + .map(|c| &c.details.as_ref().unwrap().title) + .collect::>(); + + assert_eq!( + ops, + vec![ + "CreateCommit", + "CreateBranch", + "CreateCommit", + "CreateCommit", + "CreateBranch", + "SetBaseBranch", + ] + ); + + project + .restore_snapshot(snapshots[1].clone().id) + .unwrap(); + + // restores the conflict files + let file_lines = std::fs::read_to_string(&conflicts_path).unwrap(); + assert_eq!(file_lines, "conflict A"); + let file_lines = std::fs::read_to_string(&base_merge_parent_path).unwrap(); + assert_eq!(file_lines, "parent A"); + + assert_eq!(snapshots[2].lines_added, 2); + assert_eq!(snapshots[2].lines_removed, 0); + + project + .restore_snapshot(snapshots[3].clone().id) + .unwrap(); + + // the restore removed our new branch + let branches = controller.list_virtual_branches(project_id).await.unwrap(); + assert_eq!(branches.0.len(), 1); + + // assert that the conflicts file was removed + assert!(!&conflicts_path.try_exists().unwrap()); + + // remove commit2_oid from odb + let commit_str = &commit2_id.to_string(); + // find file in odb + let file_path = repository.path().join(".git").join("objects").join(&commit_str[..2]); + let file_path = file_path.join(&commit_str[2..]); + assert!(file_path.exists()); + // remove file + std::fs::remove_file(file_path).unwrap(); + + // try to look up that object + let repo = git2::Repository::open(&project.path).unwrap(); + let commit = repo.find_commit(commit2_id.into()); + assert!(commit.is_err()); + + project + .restore_snapshot(snapshots[2].clone().id) + .unwrap(); + + // test missing commits are recreated + let commit = repo.find_commit(commit2_id.into()); + assert!(commit.is_ok()); + + let file_path = repository.path().join("large.txt"); + assert!(file_path.exists()); + + let file_path = repository.path().join("file.txt"); + let file_lines = std::fs::read_to_string(file_path).unwrap(); + assert_eq!(file_lines, "content"); +} + +// test oplog.toml head is not a commit + +#[tokio::test] +async fn test_oplog_head_corrupt() { + let Test { + repository, + project_id, + controller, + project, + .. + } = &Test::default(); + + controller + .set_base_branch(project_id, &"refs/remotes/origin/master".parse().unwrap()) + .await + .unwrap(); + + let snapshots = project.list_snapshots(10, None).unwrap(); + assert_eq!(snapshots.len(), 1); + + // overwrite oplog head with a non-commit sha + let file_path = repository.path().join(".git").join("operations-log.toml"); + fs::write(file_path, "head_sha = \"758d54f587227fba3da3b61fbb54a99c17903d59\"").unwrap(); + + controller + .set_base_branch(project_id, &"refs/remotes/origin/master".parse().unwrap()) + .await + .unwrap(); + + // it should have just reset the oplog head, so only 1, not 2 + let snapshots = project.list_snapshots(10, None).unwrap(); + assert_eq!(snapshots.len(), 1); +}