mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-05 08:29:30 +03:00
Merge pull request #4993 from gitbutlerapp/fix-create-wd-tree
Use new git2 implementation
This commit is contained in:
commit
7422110a6d
@ -327,12 +327,14 @@ impl BranchManager<'_> {
|
||||
|
||||
// We don't support having two branches applied that conflict with each other
|
||||
{
|
||||
let mut index = repo.index()?;
|
||||
index.add_all(["*"], git2::IndexAddOption::default(), None)?;
|
||||
let index_tree = index.write_tree()?;
|
||||
let index_tree = repo.find_tree(index_tree)?;
|
||||
let uncommited_changes_tree = repo.create_wd_tree()?;
|
||||
let branch_merged_with_other_applied_branches = repo
|
||||
.merge_trees(&merge_base_tree, &branch_tree, &index_tree, None)
|
||||
.merge_trees(
|
||||
&merge_base_tree,
|
||||
&branch_tree,
|
||||
&uncommited_changes_tree,
|
||||
None,
|
||||
)
|
||||
.context("failed to merge trees")?;
|
||||
|
||||
if branch_merged_with_other_applied_branches.has_conflicts() {
|
||||
|
@ -60,3 +60,103 @@ pub(crate) fn checkout_branch_trees<'a>(
|
||||
Ok(final_tree)
|
||||
}
|
||||
}
|
||||
|
||||
// These possibly could be considered more "integration" tests, but there is no
|
||||
// need for `checkout_branch_trees` to be public, so it is tested here.
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::fs;
|
||||
|
||||
use bstr::ByteSlice as _;
|
||||
use gitbutler_branch::BranchCreateRequest;
|
||||
use gitbutler_command_context::CommandContext;
|
||||
use gitbutler_repo::RepositoryExt as _;
|
||||
use gitbutler_testsupport::{paths, testing_repository::assert_tree_matches, TestProject};
|
||||
|
||||
#[test]
|
||||
fn checkout_with_two_branches() {
|
||||
let test_project = &TestProject::default();
|
||||
|
||||
let data_dir = paths::data_dir();
|
||||
let projects = gitbutler_project::Controller::from_path(data_dir.path());
|
||||
|
||||
let project = projects
|
||||
.add(test_project.path())
|
||||
.expect("failed to add project");
|
||||
|
||||
crate::set_base_branch(&project, &"refs/remotes/origin/master".parse().unwrap()).unwrap();
|
||||
|
||||
let branch_1 =
|
||||
crate::create_virtual_branch(&project, &BranchCreateRequest::default()).unwrap();
|
||||
|
||||
fs::write(test_project.path().join("foo.txt"), "content").unwrap();
|
||||
|
||||
crate::create_commit(&project, branch_1, "commit one", None, false).unwrap();
|
||||
|
||||
let branch_2 =
|
||||
crate::create_virtual_branch(&project, &BranchCreateRequest::default()).unwrap();
|
||||
|
||||
fs::write(test_project.path().join("bar.txt"), "content").unwrap();
|
||||
|
||||
crate::create_commit(&project, branch_2, "commit two", None, false).unwrap();
|
||||
|
||||
let tree = test_project.local_repository.create_wd_tree().unwrap();
|
||||
|
||||
// Assert original state
|
||||
assert_tree_matches(
|
||||
&test_project.local_repository,
|
||||
&tree,
|
||||
&[("foo.txt", b"content"), ("bar.txt", b"content")],
|
||||
);
|
||||
assert_eq!(tree.len(), 2);
|
||||
|
||||
// Checkout an empty tree
|
||||
{
|
||||
let tree_oid = test_project
|
||||
.local_repository
|
||||
.treebuilder(None)
|
||||
.unwrap()
|
||||
.write()
|
||||
.unwrap();
|
||||
let tree = test_project.local_repository.find_tree(tree_oid).unwrap();
|
||||
test_project
|
||||
.local_repository
|
||||
.checkout_tree_builder(&tree)
|
||||
.force()
|
||||
.remove_untracked()
|
||||
.checkout()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Assert tree is indeed empty
|
||||
{
|
||||
let tree: git2::Tree = test_project.local_repository.create_wd_tree().unwrap();
|
||||
dbg!(tree
|
||||
.into_iter()
|
||||
.map(|t| t.name_bytes().to_str_lossy().to_string())
|
||||
.collect::<Vec<_>>());
|
||||
|
||||
// Tree should be empty
|
||||
assert_eq!(
|
||||
tree.len(),
|
||||
0,
|
||||
"Should be empty after checking out an empty tree"
|
||||
);
|
||||
}
|
||||
|
||||
let ctx = CommandContext::open(&project).unwrap();
|
||||
let mut guard = project.exclusive_worktree_access();
|
||||
|
||||
super::checkout_branch_trees(&ctx, guard.write_permission()).unwrap();
|
||||
|
||||
let tree = test_project.local_repository.create_wd_tree().unwrap();
|
||||
|
||||
// Should be back to original state
|
||||
assert_tree_matches(
|
||||
&test_project.local_repository,
|
||||
&tree,
|
||||
&[("foo.txt", b"content"), ("bar.txt", b"content")],
|
||||
);
|
||||
assert_eq!(tree.len(), 2, "Should match original state");
|
||||
}
|
||||
}
|
||||
|
@ -253,10 +253,7 @@ pub(crate) fn save_and_return_to_workspace(
|
||||
let parents = commit.parents().collect::<Vec<_>>();
|
||||
|
||||
// Recommit commit
|
||||
let mut index = repository.index()?;
|
||||
index.add_all(["*"], git2::IndexAddOption::DEFAULT, None)?;
|
||||
let tree = index.write_tree()?;
|
||||
let tree = repository.find_tree(tree)?;
|
||||
let tree = repository.create_wd_tree()?;
|
||||
|
||||
let commit_headers = commit
|
||||
.gitbutler_headers()
|
||||
|
@ -2,12 +2,13 @@
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::path::PathBuf;
|
||||
use std::{io::Write, path::Path, process::Stdio, str};
|
||||
|
||||
use crate::{Config, LogUntil};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use bstr::{BString, ByteSlice};
|
||||
use git2::{BlameOptions, Tree};
|
||||
use bstr::BString;
|
||||
use git2::{BlameOptions, StatusOptions, Tree};
|
||||
use gitbutler_branch::SignaturePurpose;
|
||||
use gitbutler_commit::commit_headers::CommitHeadersV2;
|
||||
use gitbutler_config::git::{GbConfig, GitConfig};
|
||||
@ -17,13 +18,13 @@ use gitbutler_oxidize::{
|
||||
};
|
||||
use gitbutler_reference::{Refname, RemoteRefname};
|
||||
use gix::objs::WriteTo;
|
||||
use gix::status::plumbing::index_as_worktree::{Change, EntryStatus};
|
||||
use tracing::instrument;
|
||||
|
||||
/// Extension trait for `git2::Repository`.
|
||||
///
|
||||
/// For now, it collects useful methods from `gitbutler-core::git::Repository`
|
||||
pub trait RepositoryExt {
|
||||
fn worktree_path(&self) -> PathBuf;
|
||||
fn signatures(&self) -> Result<(git2::Signature, git2::Signature)>;
|
||||
fn l(&self, from: git2::Oid, to: LogUntil) -> Result<Vec<git2::Oid>>;
|
||||
fn list_commits(&self, from: git2::Oid, to: git2::Oid) -> Result<Vec<git2::Commit>>;
|
||||
@ -156,73 +157,67 @@ impl RepositoryExt for git2::Repository {
|
||||
/// the object database, and create a tree from it.
|
||||
///
|
||||
/// Note that right now, it doesn't skip big files.
|
||||
///
|
||||
/// It should also be noted that this will fail if run on an empty branch
|
||||
/// or if the HEAD branch has no commits
|
||||
#[instrument(level = tracing::Level::DEBUG, skip(self), err(Debug))]
|
||||
fn create_wd_tree(&self) -> Result<Tree> {
|
||||
let repo = gix::open(self.path())?;
|
||||
let workdir = repo.work_dir().context("Need non-bare repository")?;
|
||||
let mut head_tree_editor = repo.edit_tree(repo.head_tree_id()?)?;
|
||||
let status_changes = repo
|
||||
.status(gix::progress::Discard)?
|
||||
.index_worktree_rewrites(None)
|
||||
.index_worktree_submodules(None)
|
||||
.into_index_worktree_iter(None::<BString>)?;
|
||||
for change in status_changes {
|
||||
let change = change?;
|
||||
match change {
|
||||
// modified or tracked files are unconditionally added as blob.
|
||||
gix::status::index_worktree::iter::Item::Modification {
|
||||
rela_path,
|
||||
status: EntryStatus::Change(Change::Type | Change::Modification { .. }),
|
||||
..
|
||||
}
|
||||
| gix::status::index_worktree::iter::Item::DirectoryContents {
|
||||
entry:
|
||||
gix::dir::Entry {
|
||||
rela_path,
|
||||
status: gix::dir::entry::Status::Untracked,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let path = workdir.join(gix::path::from_bstr(&rela_path));
|
||||
let Ok(md) = std::fs::symlink_metadata(&path) else {
|
||||
continue;
|
||||
};
|
||||
let (id, kind) = if md.is_symlink() {
|
||||
let target = std::fs::read_link(&path).with_context(|| {
|
||||
format!(
|
||||
"Failed to read link at '{}' for adding to the object database",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
let id = repo.write_blob(gix::path::into_bstr(target).as_bytes())?;
|
||||
(id, gix::object::tree::EntryKind::Link)
|
||||
} else {
|
||||
let mut file = std::fs::File::open(&path).with_context(|| {
|
||||
format!(
|
||||
"Could not open file at '{}' for adding it to the object database",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
let kind = if gix::fs::is_executable(&md) {
|
||||
gix::object::tree::EntryKind::BlobExecutable
|
||||
} else {
|
||||
gix::object::tree::EntryKind::Blob
|
||||
};
|
||||
(repo.write_blob_stream(&mut file)?, kind)
|
||||
};
|
||||
let mut tree_update_builder = git2::build::TreeUpdateBuilder::new();
|
||||
|
||||
head_tree_editor.upsert(rela_path, kind, id)?;
|
||||
}
|
||||
gix::status::index_worktree::iter::Item::Rewrite { .. } => {
|
||||
unreachable!("disabled")
|
||||
}
|
||||
_ => {}
|
||||
let worktree_path = self.worktree_path();
|
||||
|
||||
let statuses = self.statuses(Some(
|
||||
StatusOptions::new()
|
||||
.renames_from_rewrites(false)
|
||||
.renames_head_to_index(false)
|
||||
.renames_index_to_workdir(false)
|
||||
.include_untracked(true)
|
||||
.recurse_untracked_dirs(true),
|
||||
))?;
|
||||
|
||||
// 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 |
|
||||
|
||||
for status_entry in &statuses {
|
||||
let status = status_entry.status();
|
||||
let path = status_entry.path().context("Failed to get path")?;
|
||||
let path = Path::new(path);
|
||||
|
||||
if status.is_index_new() && status.is_wt_deleted() {
|
||||
// This is a no-op
|
||||
} else if (status.is_index_deleted() && !status.is_wt_new()) || status.is_wt_deleted() {
|
||||
tree_update_builder.remove(path);
|
||||
} else {
|
||||
let file_path = worktree_path.join(path);
|
||||
let file = std::fs::read(file_path)?;
|
||||
let blob = self.blob(&file)?;
|
||||
|
||||
tree_update_builder.upsert(path, blob, git2::FileMode::Blob);
|
||||
}
|
||||
}
|
||||
|
||||
let oid = git2::Oid::from_bytes(head_tree_editor.write()?.as_bytes())?;
|
||||
self.find_tree(oid).map(Into::into).map_err(Into::into)
|
||||
let head_tree = self.head_commit()?.tree()?;
|
||||
let tree_oid = tree_update_builder.create_updated(self, &head_tree)?;
|
||||
|
||||
Ok(self.find_tree(tree_oid)?)
|
||||
}
|
||||
|
||||
/// Returns the path of the working directory of the repository.
|
||||
fn worktree_path(&self) -> PathBuf {
|
||||
if self.is_bare() {
|
||||
self.path().to_owned()
|
||||
} else {
|
||||
self.path().join("..")
|
||||
}
|
||||
}
|
||||
|
||||
fn workspace_ref_from_head(&self) -> Result<git2::Reference<'_>> {
|
||||
@ -634,3 +629,429 @@ impl GixRepositoryExt for gix::Repository {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod create_wd_tree {
|
||||
use std::path::Path;
|
||||
|
||||
use crate::repository_ext::RepositoryExt as _;
|
||||
use gitbutler_testsupport::testing_repository::{assert_tree_matches, 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")],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user