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 { 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 { 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 { 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 { 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 { 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 { 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)?) }