summaryrefslogtreecommitdiff
path: root/src/bin/git-restore-autosave.rs
blob: a8fdb9ac307ad08ba55cefb870b60d2be9271c19 (plain)
// 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, Config, 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 all_devices = std::env::args().any(|arg| arg == "--all-devices");
	let anytime = std::env::args().any(|arg| arg == "--anytime");
	let force = std::env::args().any(|arg| arg == "--force");

	let repository = Repository::discover(".")?;
	let repo_id = git_autosave::repository_id(&repository)?;
	let signature = repository.signature()?;
	let branch = git_autosave::utils::current_branch(&repository)?;
	let earliest_time = repository.head()?.peel_to_commit()?.time();

	let gitconfig = repository.config()?;
	let config: &'static _ = Box::leak(Box::new(Config::load()?));
	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)
		.filter(|autosave| all_devices || autosave.repo_id.as_bytes() != repo_id.as_bytes())
		.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, --all-devices, 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::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)?;

	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(())
}