From bebb3a3712580ecb9e00afec1e88b4155733c008 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 20 Sep 2016 15:44:12 +0200 Subject: [PATCH] Add initial codebase for store-unload hook --- libimagstorestdhook/src/vcs/git/mod.rs | 1 + .../src/vcs/git/store_unload.rs | 205 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 libimagstorestdhook/src/vcs/git/store_unload.rs diff --git a/libimagstorestdhook/src/vcs/git/mod.rs b/libimagstorestdhook/src/vcs/git/mod.rs index 1f126247..7fc504b3 100644 --- a/libimagstorestdhook/src/vcs/git/mod.rs +++ b/libimagstorestdhook/src/vcs/git/mod.rs @@ -23,6 +23,7 @@ pub mod delete; mod error; mod result; mod runtime; +pub mod store_unload; pub mod update; pub mod util; diff --git a/libimagstorestdhook/src/vcs/git/store_unload.rs b/libimagstorestdhook/src/vcs/git/store_unload.rs new file mode 100644 index 00000000..4f5da864 --- /dev/null +++ b/libimagstorestdhook/src/vcs/git/store_unload.rs @@ -0,0 +1,205 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::path::PathBuf; +use std::fmt::{Debug, Formatter, Error as FmtError}; +use std::result::Result as RResult; + +use toml::Value; + +use libimagerror::trace::trace_error; +use libimagstore::storeid::StoreId; +use libimagstore::hook::Hook; +use libimagstore::hook::result::HookResult; +use libimagstore::hook::accessor::{HookDataAccessor, HookDataAccessorProvider}; +use libimagstore::hook::accessor::StoreIdAccessor; +use libimagutil::debug_result::*; + +use vcs::git::error::GitHookErrorKind as GHEK; +use vcs::git::error::MapErrInto; +use vcs::git::runtime::Runtime as GRuntime; + +pub struct StoreUnloadHook { + storepath: PathBuf, + + runtime: GRuntime, +} + +impl StoreUnloadHook { + + pub fn new(storepath: PathBuf) -> StoreUnloadHook { + StoreUnloadHook { + runtime: GRuntime::new(&storepath), + storepath: storepath, + } + } + +} + +impl Debug for StoreUnloadHook { + fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> { + write!(fmt, "StoreUnloadHook(storepath={:?}, repository={}, cfg={:?})", + self.storepath, + (if self.runtime.has_repository() { "Some(_)" } else { "None" }), + self.runtime.has_config()) + } +} + + +impl Hook for StoreUnloadHook { + + fn name(&self) -> &'static str { + "stdhook_git_storeunload" + } + + /// Set the configuration of the hook. See + /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()`. + /// + /// This function traces the error (using `trace_error()`) that + /// `libimagstorestdhook::vcs::git::runtime::Runtime::set_config()` + /// returns, if any. + fn set_config(&mut self, config: &Value) { + if let Err(e) = self.runtime.set_config(config) { + trace_error(&e); + } + } + +} + +impl HookDataAccessorProvider for StoreUnloadHook { + + fn accessor(&self) -> HookDataAccessor { + HookDataAccessor::StoreIdAccess(self) + } +} + +impl StoreIdAccessor for StoreUnloadHook { + + fn access(&self, id: &StoreId) -> HookResult<()> { + use libimagerror::into::IntoError; + use vcs::git::action::StoreAction; + use vcs::git::config::commit_message; + use vcs::git::error::MapIntoHookError; + use vcs::git::util::fetch_index; + use vcs::git::config::abort_on_repo_init_err; + use vcs::git::config::is_enabled; + use vcs::git::config::committing_is_enabled; + + let action = StoreAction::StoreUnload; + let cfg = try!(self.runtime.config_value_or_err(&action)); + + if !is_enabled(cfg) { + return Ok(()) + } + + if !self.runtime.has_repository() { + debug!("[GIT STORE UNLOAD HOOK]: Runtime has no repository..."); + if try!(self.runtime.config_value_or_err(&action).map(|c| abort_on_repo_init_err(c))) { + // Abort on repo init failure + debug!("[GIT STORE UNLOAD HOOK]: Config says we should abort if we have no repository"); + debug!("[GIT STORE UNLOAD HOOK]: Returing Err(_)"); + return Err(GHEK::RepositoryInitError.into_error()) + .map_err_into(GHEK::RepositoryError) + .map_into_hook_error() + } else { + debug!("[GIT STORE UNLOAD HOOK]: Config says it is okay to not have a repository"); + debug!("[GIT STORE UNLOAD HOOK]: Returing Ok(())"); + return Ok(()) + } + } + + let _ = try!(self.runtime.ensure_cfg_branch_is_checked_out(&action)); + let repo = try!(self.runtime.repository(&action)); + let mut index = try!(fetch_index(repo, &action)); + + let signature = try!( + repo.signature() + .map_err_into(GHEK::MkSignature) + .map_dbg_err_str("Failed to fetch signature") + .map_dbg_str("[GIT STORE UNLOAD HOOK]: Fetched signature object") + .map_into_hook_error() + ); + + let head = try!( + repo.head() + .map_err_into(GHEK::HeadFetchError) + .map_dbg_err_str("Failed to fetch HEAD") + .map_dbg_str("[GIT STORE UNLOAD HOOK]: Fetched HEAD") + .map_into_hook_error() + ); + + let tree_id = try!( + index.write_tree() + .map_err_into(GHEK::RepositoryIndexWritingError) + .map_dbg_err_str("Failed to write tree") + .map_dbg_str("[GIT STORE UNLOAD HOOK]: Wrote index tree") + .map_into_hook_error() + ); + + if !try!(committing_is_enabled(cfg)) { + debug!("Committing not enabled. This is fine, returning now..."); + return Ok(()) + } + + let mut parents = Vec::new(); + { + let commit = try!( + repo.find_commit(head.target().unwrap()) + .map_err_into(GHEK::RepositoryParentFetchingError) + .map_dbg_err_str("Failed to find commit HEAD") + .map_dbg_str("[GIT STORE UNLOAD HOOK]: Found commit HEAD") + .map_into_hook_error() + ); + parents.push(commit); + } + + // for converting from Vec to Vec<&Commit> + let parents = parents.iter().collect::>(); + + let tree = try!( + repo.find_tree(tree_id) + .map_err_into(GHEK::RepositoryParentFetchingError) + .map_dbg_err_str("Failed to find tree") + .map_dbg_str("[GIT STORE UNLOAD HOOK]: Found tree for index") + .map_into_hook_error() + ); + + let message = try!(commit_message(&repo, cfg, action, &id) + .map_dbg_err_str("Failed to get commit message") + .map_dbg_str("[GIT STORE UNLOAD HOOK]: Got commit message")); + + try!(repo.commit(Some("HEAD"), &signature, &signature, &message, &tree, &parents) + .map_dbg_str("Committed") + .map_dbg_err_str("Failed to commit") + .map_dbg_str("[GIT STORE UNLOAD HOOK]: Committed") + .map_err_into(GHEK::RepositoryCommittingError) + .map_into_hook_error() + ); + + index.write() + .map_err_into(GHEK::RepositoryIndexWritingError) + .map_dbg_err_str("Failed to write tree") + .map_dbg_str("[GIT STORE UNLOAD HOOK]: Wrote index") + .map_into_hook_error() + .map(|_| ()) + } + +} + +