use std::path::Path;
use git2::{Commit, MergeOptions, Oid, Repository, Tree};
use is_executable::is_executable;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum TreeError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Git(#[from] git2::Error),
}
pub fn current_branch_ref(repository: &Repository) -> Result<String, git2::Error> {
let head = repository.head()?;
let ref_name = match head.kind() {
Some(git2::ReferenceType::Symbolic) => head.symbolic_target(),
_ => head.name(),
}
.ok_or(git2::Error::new(
git2::ErrorCode::Invalid,
git2::ErrorClass::Reference,
"The current branch's name is not valid UTF-8",
))?
.to_string();
Ok(ref_name)
}
pub fn current_branch(repository: &Repository) -> Result<String, git2::Error> {
let ref_name = current_branch_ref(repository)?;
let branch_name = ref_name
.strip_prefix("refs/heads/")
.unwrap_or(&ref_name)
.to_string();
Ok(branch_name)
}
pub fn filemode_for_dir_entry(path: &Path) -> std::io::Result<i32> {
let metadata = path.metadata()?;
Ok(if metadata.is_dir() {
0o040000
} else if metadata.is_symlink() {
0o120000
} else if is_executable(path) {
0o100755
} else {
0o100644
})
}
pub fn path_to_tree(repository: &Repository, path: &Path) -> Result<Oid, TreeError> {
let workdir = repository.workdir().expect("a non-bare repo");
if path.is_dir() {
let mut tree = repository.treebuilder(None)?;
for entry in path.read_dir()? {
let entry = entry?;
let full_path = entry.path();
let relative_path = full_path.strip_prefix(workdir).unwrap_or(path);
if repository.is_path_ignored(relative_path)? {
continue;
}
let filemode = filemode_for_dir_entry(&entry.path())?;
let oid = path_to_tree(repository, &entry.path())?;
let filename = entry.file_name();
tree.insert(filename, oid, filemode)?;
}
Ok(tree.write()?)
} else {
Ok(repository.blob_path(path)?)
}
}
pub fn workdir_to_tree(repository: &Repository) -> Result<Oid, TreeError> {
let Some(workdir) = repository.workdir() else {
return Err(TreeError::Git(git2::Error::new(
git2::ErrorCode::BareRepo,
git2::ErrorClass::Repository,
"git-autosave does not support bare repositories",
)));
};
path_to_tree(repository, workdir)
}
pub fn merge_commit_with_tree(
repository: &Repository,
commit: &Commit<'_>,
workdir: &Tree<'_>,
) -> Result<Oid, TreeError> {
Ok(repository
.merge_trees(
&repository
.find_commit(
repository
.merge_base(repository.head()?.peel_to_commit()?.id(), commit.id())?,
)?
.tree()?,
workdir,
&commit.tree()?,
Some(MergeOptions::new().find_renames(true).patience(true)),
)?
.write_tree_to(repository)?)
}
|