summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2026-03-30 21:12:45 -0400
committerMica White <botahamec@outlook.com>2026-03-30 21:12:45 -0400
commitb464fb60ac8295ef3678f59898c89e0a912457d2 (patch)
tree82314d635112c75e53f74902d8bcf5ff3e6aaf5a /src
parent6495268f002e1eeb27d13c58eec34ac4fa9b4ee6 (diff)
Create utils module
Diffstat (limited to 'src')
-rw-r--r--src/bin/git-restore-autosave.rs7
-rw-r--r--src/lib.rs239
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)?;
diff --git a/src/lib.rs b/src/lib.rs
index dd8ee86..ecc202f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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(&current_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(&current_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(