diff options
Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 184 |
1 files changed, 182 insertions, 2 deletions
@@ -15,6 +15,7 @@ * - check default merge strategy * - merge autosave using default strategy * - allow a different merge strategy to be specified + * - allow --no-commit * - display available autosaves if there is more than one * * git undo-restore-autosave: @@ -31,7 +32,10 @@ use std::fs::Metadata; use std::path::{Path, PathBuf}; use confy::ConfyError; -use git2::{Oid, PushOptions, RemoteCallbacks, Repository}; +use git2::{ + Commit, FetchOptions, MergeOptions, Oid, PushOptions, Reference, RemoteCallbacks, Repository, + Signature, Time, Tree, +}; use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; @@ -79,6 +83,19 @@ pub fn save_config(config: &Config) -> Result<(), ConfyError> { confy::store("git-autosave", "git-autosaved", config) } +pub fn repository_id(repository: &Repository) -> Result<String, git2::Error> { + repository + .config()? + .get_entry("autosave.id")? + .value() + .map(|s| s.to_string()) + .ok_or(git2::Error::new( + git2::ErrorCode::Invalid, + git2::ErrorClass::Config, + "Repository ID is not valid UTF-8", + )) +} + pub fn init(repository: &Repository, config: Option<&mut Config>) -> Result<Uuid, git2::Error> { let id = Uuid::new_v4(); let workdir = repository.workdir(); @@ -192,6 +209,7 @@ pub fn push_autosaves( repository: &Repository, callbacks: RemoteCallbacks<'_>, ) -> Result<(), git2::Error> { + let id = repository_id(repository)?; let remote = repository.branch_upstream_remote(¤t_branch_ref(repository)?)?; let mut remote = repository.find_remote(&String::from_utf8_lossy(&remote))?; let refs = repository @@ -200,7 +218,7 @@ pub fn push_autosaves( .filter(|reference| { reference .name_bytes() - .starts_with(b"refs/autosave/autosaves") + .starts_with(format!("refs/autosave/autosaves/{id}").as_bytes()) }) .filter_map(|reference| reference.name().map(|n| n.to_string())) .map(|reference| format!("+{reference}:{reference}")) @@ -209,3 +227,165 @@ pub fn push_autosaves( Ok(()) } + +pub fn fetch_autosaves( + repository: &Repository, + callbacks: RemoteCallbacks<'_>, +) -> Result<(), git2::Error> { + let remote = repository.branch_upstream_remote(¤t_branch_ref(repository)?)?; + let mut remote = repository.find_remote(&String::from_utf8_lossy(&remote))?; + remote.fetch( + &["+refs/autosave/autosaves/*:refs/autosave/autosaves/*"], + Some(FetchOptions::new().remote_callbacks(callbacks)), + Some("Updated 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()? + .filter_map(|reference| reference.ok()) + .filter(|reference| { + reference + .name_bytes() + .starts_with(b"refs/autosave/autosaves") + }) + .filter_map(|reference| reference.try_into().ok()) + .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()?) +} + +pub fn save_undo_tree(repository: &Repository, workdir: &Tree<'_>) -> Result<(), TreeError> { + let signature = repository.signature()?; + let commit = repository.commit( + None, + &signature, + &signature, + "Work done before autosave was restored", + workdir, + &[&repository.head()?.peel_to_commit()?], + )?; + repository.reference( + "refs/autosave/undo", + commit, + true, + "Save work before restoring autosave", + )?; + + Ok(()) +} + +pub fn saved_restored_autosave( + repository: &Repository, + autosave: &Autosave, +) -> Result<(), git2::Error> { + let author = if let Some(author) = &autosave.author + && let Some(email) = &autosave.email + { + Signature::new(author, email, &autosave.time)? + } else { + repository.signature()? + }; + let committer = repository.signature()?; + let message = format!( + "{}:{}/{}", + autosave.host_name.clone().unwrap_or_default(), + &autosave.repo_id, + &autosave.branch_name + ); + let tree = repository.find_commit(autosave.commit_id)?.tree()?; + let parent = repository + .find_reference("refs/autosave/restored") + .ok() + .and_then(|reference| reference.peel_to_commit().ok()); + let parents = parent.iter().collect::<Vec<_>>(); + + repository.commit( + Some("refs/autosave/restored"), + &author, + &committer, + &message, + &tree, + &parents, + )?; + + Ok(()) +} |
