diff options
Diffstat (limited to 'src/bin/git-restore-autosave.rs')
| -rw-r--r-- | src/bin/git-restore-autosave.rs | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/src/bin/git-restore-autosave.rs b/src/bin/git-restore-autosave.rs new file mode 100644 index 0000000..51cc14c --- /dev/null +++ b/src/bin/git-restore-autosave.rs @@ -0,0 +1,128 @@ +// git restore-autosave +// - --user and --all-users cannot both be present +// - --branch and --all-branches cannot both be present +// - if --user or -u is not present, the selected user is the repository signature +// - if --branch or -b is not present, the selected branch is the checked out branch +// - if --device or -d is present (UUID or hostname), filter to autosaves from that device +// - filter to autosaves from the current user (name or email) if --all-users is not present +// - filter to autosaves on the current branch +// - filter to autosaves after the head commit +// - if there is more than one option, enter pick mode +// - if there are no options, tell the user to use --all-users or --all-branches or --anytime + +use std::fmt::Display; + +use auth_git2::GitAuthenticator; +use chrono::Local; +use git_autosave::{Autosave, authenticate::Inquirer}; +use git2::{RemoteCallbacks, Repository, build::CheckoutBuilder}; + +struct AutosaveOption { + text: String, + value: Autosave, +} + +impl Display for AutosaveOption { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.text.fmt(f) + } +} + +fn main() -> Result<(), anyhow::Error> { + let all_users = std::env::args().any(|arg| arg == "--all-users"); + let all_branches = std::env::args().any(|arg| arg == "--all-branches"); + let anytime = std::env::args().any(|arg| arg == "--anytime"); + let force = std::env::args().any(|arg| arg == "--force"); + + let repository = Repository::discover(".")?; + let signature = repository.signature()?; + let branch = git_autosave::current_branch(&repository)?; + let earliest_time = repository.head()?.peel_to_commit()?.time(); + + let gitconfig = repository.config()?; + let config: &'static _ = Box::leak(Box::new(git_autosave::load_config()?)); + let auth = GitAuthenticator::new().set_prompter(Inquirer(config)); + let mut callbacks = RemoteCallbacks::new(); + callbacks.credentials(auth.credentials(&gitconfig)); + git_autosave::fetch_autosaves(&repository, callbacks)?; + + let mut autosaves = git_autosave::autosaves(&repository)? + .into_iter() + .filter(|autosave| { + all_users + || signature + .name() + .zip(autosave.author.clone()) + .is_some_and(|(a, b)| a == b) + || signature + .email() + .zip(autosave.email.clone()) + .is_some_and(|(a, b)| a == b) + }) + .filter(|autosave| all_branches || autosave.branch_name == branch) + .filter(|autosave| anytime || autosave.time > earliest_time) + .collect::<Vec<_>>(); + + if autosaves.is_empty() { + eprintln!("ERROR: There are no available autosaves for the given filters."); + if !all_users || !all_branches || !anytime { + eprintln!( + "hint: Use --all-users, --all-branches, or --anytime to include more options." + ); + } + std::process::exit(1); + } + + let autosave = if autosaves.len() > 1 { + let autosaves = autosaves + .into_iter() + .map(|autosave| { + let device = autosave.host_name.as_ref().unwrap_or(&autosave.repo_id); + let time = chrono::DateTime::from_timestamp(autosave.time.seconds(), 0) + .map(|time| time.with_timezone(&Local)) + .map(|time| time.to_rfc2822()) + .unwrap_or(autosave.time.seconds().to_string()); + let branch = if all_branches { + format!(" on {}", &autosave.branch_name) + } else { + String::new() + }; + let author = if let Some(author) = + autosave.author.as_ref().or(autosave.email.as_ref()) + && all_users + { + format!(" by {author}") + } else { + String::new() + }; + AutosaveOption { + text: format!("{device} ({time}{branch}{author})"), + value: autosave, + } + }) + .collect(); + inquire::Select::new("Select an autosave:", autosaves) + .prompt()? + .value + } else { + autosaves + .pop() + .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)?; + git_autosave::save_undo_tree(&repository, &workdir)?; + git_autosave::saved_restored_autosave(&repository, &autosave)?; + + let mut options = CheckoutBuilder::new(); + if force { + options.force(); + } + repository.checkout_tree( + &repository.find_object(new_tree, Some(git2::ObjectType::Tree))?, + Some(&mut options), + )?; + + Ok(()) +} |
