diff options
| -rw-r--r-- | src/bin/git-restore-autosave.rs | 7 | ||||
| -rw-r--r-- | src/lib.rs | 239 |
2 files changed, 67 insertions, 179 deletions
diff --git a/src/bin/git-restore-autosave.rs b/src/bin/git-restore-autosave.rs index b948cc2..e02bb90 100644 --- a/src/bin/git-restore-autosave.rs +++ b/src/bin/git-restore-autosave.rs @@ -39,7 +39,7 @@ fn main() -> Result<(), anyhow::Error> { let gitconfig = repository.config()?; let repo_id = gitconfig.get_entry("autosave.id")?; let signature = repository.signature()?; - let branch = git_autosave::current_branch(&repository)?; + let branch = git_autosave::utils::current_branch(&repository)?; let earliest_time = repository.head()?.peel_to_commit()?.time(); let config: &'static _ = Box::leak(Box::new(Config::load()?)); @@ -113,8 +113,9 @@ fn main() -> Result<(), anyhow::Error> { .expect("There should be an autosave to select but there isn't. This is a bug!") }; let autosaved_commit = repository.find_commit(autosave.commit_id)?; - let workdir = repository.find_tree(git_autosave::workdir_to_tree(&repository)?)?; - let new_tree = git_autosave::merge_commit_with_tree(&repository, &autosaved_commit, &workdir)?; + let workdir = repository.find_tree(git_autosave::utils::workdir_to_tree(&repository)?)?; + let new_tree = + git_autosave::utils::merge_commit_with_tree(&repository, &autosaved_commit, &workdir)?; git_autosave::save_undo_tree(&repository, &workdir)?; git_autosave::saved_restored_autosave(&repository, &autosave)?; @@ -1,15 +1,4 @@ /* - * git-restore-autosave: - * - fetch upstream's autosaves - * - filter to autosaves from the local user and branch - * - display available autosaves if there is more than one - * - check for conflicts between workdir and autosave - * - save workdir to `refs/autosave/undo` - * - copy autosave to `refs/autosave/restored` with new commit - * - apply tree to workdir - * - allow `git restore-autosave --force` - * - allow a device to be specified by name or UUID - * * git-merge-autosave: * - error is workdir is dirty * - check default merge strategy @@ -27,27 +16,74 @@ * git repair-autosave */ -use std::path::Path; - use git2::{ - Commit, FetchOptions, MergeOptions, Oid, PushOptions, Reference, RemoteCallbacks, Repository, - Signature, Time, Tree, + FetchOptions, Oid, PushOptions, Reference, RemoteCallbacks, Repository, Signature, Time, Tree, }; -use is_executable::is_executable; -use thiserror::Error; use uuid::Uuid; pub mod authenticate; pub mod config; +pub mod utils; pub use config::Config; -#[derive(Debug, Error)] -pub enum TreeError { - #[error(transparent)] - Io(#[from] std::io::Error), - #[error(transparent)] - Git(#[from] git2::Error), +use utils::TreeError; + +pub struct Autosave { + pub repo_id: String, + pub branch_name: String, + pub commit_id: Oid, + pub author: Option<String>, + pub email: Option<String>, + pub host_name: Option<String>, + pub time: Time, +} + +impl TryFrom<Reference<'_>> for Autosave { + type Error = git2::Error; + + fn try_from(value: Reference<'_>) -> Result<Self, Self::Error> { + Self::try_from(&value) + } +} + +impl TryFrom<&Reference<'_>> for Autosave { + type Error = git2::Error; + + fn try_from(reference: &Reference<'_>) -> Result<Self, Self::Error> { + let reference_name = reference.name().ok_or(git2::Error::new( + git2::ErrorCode::Invalid, + git2::ErrorClass::Reference, + "Reference is not valid UTF-8", + ))?; + let reference_name = reference_name + .strip_prefix("refs/autosave/autosaves/") + .unwrap_or(reference_name); + let Some((id, branch)) = reference_name.split_once('/') else { + return Err(git2::Error::new( + git2::ErrorCode::Invalid, + git2::ErrorClass::Reference, + "Reference does not follow format of refs/autosave/autosaves/{UUID}/{BRANCH}", + )); + }; + let commit = reference.peel_to_commit()?; + let message = commit.message(); + let host_name = message.and_then(|m| m.strip_prefix("Autosave: ")); + let author = commit.author(); + let author_name = author.name(); + let author_email = author.email(); + let time = commit.author().when(); + + Ok(Autosave { + repo_id: id.to_string(), + branch_name: branch.to_string(), + commit_id: commit.id(), + author: author_name.map(|s| s.to_string()), + email: author_email.map(|s| s.to_string()), + host_name: host_name.map(|s| s.to_string()), + time, + }) + } } pub fn repository_id(repository: &Repository) -> Result<String, git2::Error> { @@ -79,87 +115,15 @@ pub fn init(repository: &Repository, config: Option<&mut Config>) -> Result<Uuid Ok(id) } -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 only supports worktrees", - ))); - }; - - path_to_tree(repository, workdir) -} - pub fn commit_autosave(repository: &Repository) -> Result<Oid, TreeError> { let config = repository.config()?; let head = repository.head()?; let uid = config.get_entry("autosave.id")?; let uid = String::from_utf8_lossy(uid.value_bytes()); - let branch = current_branch(repository)?; + let branch = utils::current_branch(repository)?; let refname = &format!("refs/autosave/autosaves/{uid}/{branch}"); let signature = repository.signature()?; - let tree = repository.find_tree(workdir_to_tree(repository)?)?; + let tree = repository.find_tree(utils::workdir_to_tree(repository)?)?; let parent = head.peel_to_commit()?; let hostname = hostname::get(); let hostname = hostname @@ -178,7 +142,7 @@ pub fn push_autosaves( callbacks: RemoteCallbacks<'_>, ) -> Result<(), git2::Error> { let id = repository_id(repository)?; - let remote = repository.branch_upstream_remote(¤t_branch_ref(repository)?)?; + let remote = repository.branch_upstream_remote(&utils::current_branch_ref(repository)?)?; let mut remote = repository.find_remote(&String::from_utf8_lossy(&remote))?; let refs = repository .references()? @@ -200,7 +164,7 @@ pub fn fetch_autosaves( repository: &Repository, callbacks: RemoteCallbacks<'_>, ) -> Result<(), git2::Error> { - let remote = repository.branch_upstream_remote(¤t_branch_ref(repository)?)?; + let remote = repository.branch_upstream_remote(&utils::current_branch_ref(repository)?)?; let mut remote = repository.find_remote(&String::from_utf8_lossy(&remote))?; remote.fetch( &["+refs/autosave/autosaves/*:refs/autosave/autosaves/*"], @@ -211,63 +175,6 @@ pub fn fetch_autosaves( Ok(()) } -pub struct Autosave { - pub repo_id: String, - pub branch_name: String, - pub commit_id: Oid, - pub author: Option<String>, - pub email: Option<String>, - pub host_name: Option<String>, - pub time: Time, -} - -impl TryFrom<Reference<'_>> for Autosave { - type Error = git2::Error; - - fn try_from(value: Reference<'_>) -> Result<Self, Self::Error> { - Self::try_from(&value) - } -} - -impl TryFrom<&Reference<'_>> for Autosave { - type Error = git2::Error; - - fn try_from(reference: &Reference<'_>) -> Result<Self, Self::Error> { - let reference_name = reference.name().ok_or(git2::Error::new( - git2::ErrorCode::Invalid, - git2::ErrorClass::Reference, - "Reference is not valid UTF-8", - ))?; - let reference_name = reference_name - .strip_prefix("refs/autosave/autosaves/") - .unwrap_or(reference_name); - let Some((id, branch)) = reference_name.split_once('/') else { - return Err(git2::Error::new( - git2::ErrorCode::Invalid, - git2::ErrorClass::Reference, - "Reference does not follow format of refs/autosave/autosaves/{UUID}/{BRANCH}", - )); - }; - let commit = reference.peel_to_commit()?; - let message = commit.message(); - let host_name = message.and_then(|m| m.strip_prefix("Autosave: ")); - let author = commit.author(); - let author_name = author.name(); - let author_email = author.email(); - let time = commit.author().when(); - - Ok(Autosave { - repo_id: id.to_string(), - branch_name: branch.to_string(), - commit_id: commit.id(), - author: author_name.map(|s| s.to_string()), - email: author_email.map(|s| s.to_string()), - host_name: host_name.map(|s| s.to_string()), - time, - }) - } -} - pub fn autosaves(repository: &Repository) -> Result<Vec<Autosave>, git2::Error> { Ok(repository .references()? @@ -281,26 +188,6 @@ pub fn autosaves(repository: &Repository) -> Result<Vec<Autosave>, git2::Error> .collect()) } -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)?) -} - pub fn save_undo_tree(repository: &Repository, workdir: &Tree<'_>) -> Result<(), TreeError> { let signature = repository.signature()?; let commit = repository.commit( |
