rewrite our oplog tests

This commit is contained in:
Scott Chacon 2024-05-17 15:11:09 +02:00
parent 1cbcbb3478
commit dcc1b37331
3 changed files with 197 additions and 187 deletions

View File

@ -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<String> {
.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
}

View File

@ -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<TempDir>,
@ -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;

View File

@ -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::<Vec<_>>();
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);
}