mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-22 09:01:45 +03:00
516 lines
16 KiB
Rust
516 lines
16 KiB
Rust
use std::{
|
|
fs::{File, Permissions},
|
|
path::Path,
|
|
};
|
|
|
|
use gitbutler_repo::RepositoryExt as _;
|
|
use gitbutler_testsupport::testing_repository::{
|
|
assert_tree_matches, assert_tree_matches_with_mode, EntryAttribute, TestingRepository,
|
|
};
|
|
|
|
/// These tests exercise the truth table that we use to update the HEAD
|
|
/// tree to match the worktree.
|
|
///
|
|
/// Truth table for upsert/remove:
|
|
/// | HEAD Tree -> Index | Index -> Worktree | Action |
|
|
/// | add | delete | no-action |
|
|
/// | modify | delete | remove |
|
|
/// | | delete | remove |
|
|
/// | delete | | remove |
|
|
/// | delete | add | upsert |
|
|
/// | add | | upsert |
|
|
/// | | add | upsert |
|
|
/// | add | modify | upsert |
|
|
/// | modify | modify | upsert |
|
|
#[cfg(test)]
|
|
mod head_upsert_truthtable {
|
|
use super::*;
|
|
|
|
// | add | delete | no-action |
|
|
#[test]
|
|
fn index_new_worktree_delete() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content1").unwrap();
|
|
|
|
let mut index = test_repository.repository.index().unwrap();
|
|
index.add_path(Path::new("file1.txt")).unwrap();
|
|
index.write().unwrap();
|
|
|
|
std::fs::remove_file(test_repository.tempdir.path().join("file1.txt")).unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
assert_eq!(tree.len(), 0, "Tree should end up empty");
|
|
}
|
|
|
|
// | modify | delete | remove |
|
|
#[test]
|
|
fn index_modify_worktree_delete() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
|
|
|
let mut index = test_repository.repository.index().unwrap();
|
|
index.add_path(Path::new("file1.txt")).unwrap();
|
|
index.write().unwrap();
|
|
|
|
std::fs::remove_file(test_repository.tempdir.path().join("file1.txt")).unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
assert_eq!(tree.len(), 0, "Tree should end up empty");
|
|
}
|
|
|
|
// | | delete | remove |
|
|
#[test]
|
|
fn worktree_delete() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::fs::remove_file(test_repository.tempdir.path().join("file1.txt")).unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
assert_eq!(tree.len(), 0, "Tree should end up empty");
|
|
}
|
|
|
|
// | delete | | remove |
|
|
#[test]
|
|
fn index_delete() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
let mut index = test_repository.repository.index().unwrap();
|
|
index.remove_all(["*"], None).unwrap();
|
|
index.write().unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
// We should ignore whatever happens to the index
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[("file1.txt", b"content1")],
|
|
);
|
|
}
|
|
|
|
// | delete | add | upsert |
|
|
#[test]
|
|
fn index_delete_worktree_add() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
let mut index = test_repository.repository.index().unwrap();
|
|
index.remove_all(["*"], None).unwrap();
|
|
index.write().unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
// Tree should match whatever is written on disk
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[("file1.txt", b"content2")],
|
|
);
|
|
}
|
|
|
|
// | add | | upsert |
|
|
#[test]
|
|
fn index_add() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
|
|
|
let mut index = test_repository.repository.index().unwrap();
|
|
index.add_path(Path::new("file1.txt")).unwrap();
|
|
index.write().unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
// Tree should match whatever is written on disk
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[("file1.txt", b"content2")],
|
|
);
|
|
}
|
|
|
|
// | | add | upsert |
|
|
#[test]
|
|
fn worktree_add() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
// Tree should match whatever is written on disk
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[("file1.txt", b"content2")],
|
|
);
|
|
}
|
|
|
|
// | add | modify | upsert |
|
|
#[test]
|
|
fn index_add_worktree_modify() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content1").unwrap();
|
|
|
|
let mut index = test_repository.repository.index().unwrap();
|
|
index.add_path(Path::new("file1.txt")).unwrap();
|
|
index.write().unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
// Tree should match whatever is written on disk
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[("file1.txt", b"content2")],
|
|
);
|
|
}
|
|
|
|
// | modify | modify | upsert |
|
|
#[test]
|
|
fn index_modify_worktree_modify() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content2").unwrap();
|
|
|
|
let mut index = test_repository.repository.index().unwrap();
|
|
index.add_path(Path::new("file1.txt")).unwrap();
|
|
index.write().unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content3").unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
// Tree should match whatever is written on disk
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[("file1.txt", b"content3")],
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn lists_uncommited_changes() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
// Initial commit
|
|
// Create wd tree requires the HEAD branch to exist and for there
|
|
// to be at least one commit on that branch.
|
|
let commit = test_repository.commit_tree(None, &[]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content1").unwrap();
|
|
std::fs::write(test_repository.tempdir.path().join("file2.txt"), "content2").unwrap();
|
|
|
|
let tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[("file1.txt", b"content1"), ("file2.txt", b"content2")],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn does_not_include_staged_but_deleted_files() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
// Initial commit
|
|
// Create wd tree requires the HEAD branch to exist and for there
|
|
// to be at least one commit on that branch.
|
|
let commit = test_repository.commit_tree(None, &[]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file1.txt"), "content1").unwrap();
|
|
std::fs::write(test_repository.tempdir.path().join("file2.txt"), "content2").unwrap();
|
|
|
|
std::fs::write(test_repository.tempdir.path().join("file3.txt"), "content2").unwrap();
|
|
let mut index: git2::Index = test_repository.repository.index().unwrap();
|
|
index.add_path(Path::new("file3.txt")).unwrap();
|
|
index.write().unwrap();
|
|
std::fs::remove_file(test_repository.tempdir.path().join("file3.txt")).unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[("file1.txt", b"content1"), ("file2.txt", b"content2")],
|
|
);
|
|
assert!(tree.get_name("file3.txt").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn should_be_empty_after_checking_out_empty_tree() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(
|
|
None,
|
|
&[("file1.txt", "content1"), ("file2.txt", "content2")],
|
|
);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
// Checkout an empty tree
|
|
{
|
|
let tree_oid = test_repository
|
|
.repository
|
|
.treebuilder(None)
|
|
.unwrap()
|
|
.write()
|
|
.unwrap();
|
|
let tree = test_repository.repository.find_tree(tree_oid).unwrap();
|
|
test_repository
|
|
.repository
|
|
.checkout_tree_builder(&tree)
|
|
.force()
|
|
.remove_untracked()
|
|
.checkout()
|
|
.unwrap();
|
|
}
|
|
|
|
assert!(!test_repository.tempdir.path().join("file1.txt").exists());
|
|
assert!(!test_repository.tempdir.path().join("file2.txt").exists());
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
// Fails because `create_wd_tree` uses the head commit as the base,
|
|
// and then performs modifications to the tree
|
|
assert_eq!(tree.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn should_track_deleted_files() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(
|
|
None,
|
|
&[("file1.txt", "content1"), ("file2.txt", "content2")],
|
|
);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
// Make sure the index is empty, perhaps the user did this action
|
|
let mut index: git2::Index = test_repository.repository.index().unwrap();
|
|
index.remove_all(["*"], None).unwrap();
|
|
index.write().unwrap();
|
|
|
|
std::fs::remove_file(test_repository.tempdir.path().join("file1.txt")).unwrap();
|
|
|
|
assert!(!test_repository.tempdir.path().join("file1.txt").exists());
|
|
assert!(test_repository.tempdir.path().join("file2.txt").exists());
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
// Fails because `create_wd_tree` uses the head commit as the base,
|
|
// and then performs modifications to the tree
|
|
assert!(tree.get_name("file1.txt").is_none());
|
|
assert!(tree.get_name("file2.txt").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn should_not_change_index() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[("file1.txt", "content1")]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
let mut index = test_repository.repository.index().unwrap();
|
|
index.remove_all(["*"], None).unwrap();
|
|
index.write().unwrap();
|
|
|
|
let index_tree = index.write_tree().unwrap();
|
|
let index_tree = test_repository.repository.find_tree(index_tree).unwrap();
|
|
assert_eq!(index_tree.len(), 0);
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
let mut index = test_repository.repository.index().unwrap();
|
|
let index_tree = index.write_tree().unwrap();
|
|
let index_tree = test_repository.repository.find_tree(index_tree).unwrap();
|
|
assert_eq!(index_tree.len(), 0);
|
|
|
|
// Tree should match whatever is written on disk
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[("file1.txt", b"content1")],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn tree_behavior() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(
|
|
None,
|
|
&[
|
|
("dir1/file1.txt", "content1"),
|
|
("dir2/file2.txt", "content2"),
|
|
],
|
|
);
|
|
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
// Update a file in a directory
|
|
std::fs::write(
|
|
test_repository.tempdir.path().join("dir1/file1.txt"),
|
|
"new1",
|
|
)
|
|
.unwrap();
|
|
// Make a new directory and file
|
|
std::fs::create_dir(test_repository.tempdir.path().join("dir3")).unwrap();
|
|
std::fs::write(
|
|
test_repository.tempdir.path().join("dir3/file1.txt"),
|
|
"new2",
|
|
)
|
|
.unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
assert_tree_matches(
|
|
&test_repository.repository,
|
|
&tree,
|
|
&[
|
|
("dir1/file1.txt", b"new1"),
|
|
("dir2/file2.txt", b"content2"),
|
|
("dir3/file1.txt", b"new2"),
|
|
],
|
|
);
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn executable_blobs() {
|
|
use std::{io::Write, os::unix::fs::PermissionsExt as _};
|
|
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
let mut file = File::create(test_repository.tempdir.path().join("file1.txt")).unwrap();
|
|
file.set_permissions(Permissions::from_mode(0o755)).unwrap();
|
|
file.write_all(b"content1").unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
assert_tree_matches_with_mode(
|
|
&test_repository.repository,
|
|
tree.id(),
|
|
&[(
|
|
"file1.txt",
|
|
b"content1",
|
|
&[EntryAttribute::Blob, EntryAttribute::Executable],
|
|
)],
|
|
);
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn links() {
|
|
let test_repository = TestingRepository::open();
|
|
|
|
let commit = test_repository.commit_tree(None, &[("target", "helloworld")]);
|
|
test_repository
|
|
.repository
|
|
.branch("master", &commit, true)
|
|
.unwrap();
|
|
|
|
std::os::unix::fs::symlink("target", test_repository.tempdir.path().join("link1.txt")).unwrap();
|
|
|
|
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
|
|
|
assert_tree_matches_with_mode(
|
|
&test_repository.repository,
|
|
tree.id(),
|
|
&[
|
|
("link1.txt", b"target", &[EntryAttribute::Link]),
|
|
("target", b"helloworld", &[EntryAttribute::Blob]),
|
|
],
|
|
);
|
|
}
|