diff --git a/imagrc.toml b/imagrc.toml index ac323962..83f7e480 100644 --- a/imagrc.toml +++ b/imagrc.toml @@ -66,20 +66,6 @@ ensure_branch = "master" # Try to checkout the ensure_branch if it isn't checked out try_checkout_ensure_branch = true -[store.hooks.stdhook_git_delete] -aspect = "vcs" - -# Fail if the repository cannot be opened. If this is set to `false`, the error -# will be printed, but will not abort the store operation. `true` will print the -# error and abort the store action. -abort_on_repo_init_failure = true - -# Ensure to be on this branche before doing anything. -ensure_branch = "master" - -# Try to checkout the ensure_branch if it isn't checked out -try_checkout_ensure_branch = true - [store.hooks.stdhook_git_update] aspect = "vcs" @@ -108,3 +94,31 @@ interactive_editor = false # Commit message if the commit is not interactive message = "Update" +[store.hooks.stdhook_git_delete] +aspect = "vcs" + +# Fail if the repository cannot be opened. If this is set to `false`, the error +# will be printed, but will not abort the store operation. `true` will print the +# error and abort the store action. +abort_on_repo_init_failure = true + +# Ensure to be on this branche before doing anything. +ensure_branch = "master" + +# Try to checkout the ensure_branch if it isn't checked out +try_checkout_ensure_branch = true + +# Commit configuration +[store.hooks.stdhook_git_delete.commit] + +# Whether to do the commit interactively +interactive = false + +# Set to true to use the $EDITOR for the commit, to false to do on commandline +# When committing without editor, only a single line is allowed as commit +# message +interactive_editor = false + +# Commit message if the commit is not interactive +message = "Deleted" + diff --git a/libimagrt/src/runtime.rs b/libimagrt/src/runtime.rs index a942b261..05eb3506 100644 --- a/libimagrt/src/runtime.rs +++ b/libimagrt/src/runtime.rs @@ -139,7 +139,7 @@ impl<'a> Runtime<'a> { let sp = storepath; let hooks : Vec<(Box, &str, HP)> = vec![ - (Box::new(GitDeleteHook::new(sp.clone(), HP::PreDelete)) , "vcs", HP::PreDelete), + (Box::new(GitDeleteHook::new(sp.clone(), HP::PostDelete)) , "vcs", HP::PostDelete), (Box::new(GitUpdateHook::new(sp, HP::PostUpdate)) , "vcs", HP::PostUpdate), ]; diff --git a/libimagstorestdhook/src/vcs/git/delete.rs b/libimagstorestdhook/src/vcs/git/delete.rs index 902e5096..63e9f65c 100644 --- a/libimagstorestdhook/src/vcs/git/delete.rs +++ b/libimagstorestdhook/src/vcs/git/delete.rs @@ -1,42 +1,70 @@ use std::path::PathBuf; +use std::path::Path; +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::position::HookPosition; 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; -#[derive(Debug)] pub struct DeleteHook { storepath: PathBuf, + runtime: GRuntime, + position: HookPosition, - config: Option, } impl DeleteHook { pub fn new(storepath: PathBuf, p: HookPosition) -> DeleteHook { DeleteHook { + runtime: GRuntime::new(&storepath), storepath: storepath, position: p, - config: None, } } } +impl Debug for DeleteHook { + fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> { + write!(fmt, "DeleteHook(storepath={:?}, repository={}, pos={:?}, cfg={:?})", + self.storepath, + (if self.runtime.has_repository() { "Some(_)" } else { "None" }), + self.position, + self.runtime.has_config()) + } +} + + impl Hook for DeleteHook { fn name(&self) -> &'static str { "stdhook_git_delete" } + /// 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) { - self.config = Some(config.clone()); + if let Err(e) = self.runtime.set_config(config) { + trace_error(&e); + } } } @@ -51,8 +79,103 @@ impl HookDataAccessorProvider for DeleteHook { impl StoreIdAccessor for DeleteHook { fn access(&self, id: &StoreId) -> HookResult<()> { + use vcs::git::action::StoreAction; + use vcs::git::config::commit_message; + use vcs::git::error::MapIntoHookError; + use vcs::git::util::fetch_index; + use git2::{ADD_DEFAULT, STATUS_WT_DELETED, IndexMatchedPath}; + debug!("[GIT DELETE HOOK]: {:?}", id); - Ok(()) + + let action = StoreAction::Delete; + let cfg = try!(self.runtime.config_value_or_err(&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_into_hook_error() + ); + + let head = try!( + repo.head() + .map_err_into(GHEK::HeadFetchError) + .map_dbg_err_str("Failed to fetch HEAD") + .map_into_hook_error() + ); + + let file_status = try!( + repo + .status_file(id.local()) + .map_dbg_err_str("Failed to fetch file status") + .map_dbg_err(|e| format!("\t-> {:?}", e)) + .map_err_into(GHEK::RepositoryFileStatusError) + .map_into_hook_error() + ); + + let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 { + debug!("[GIT DELETE HOOK]: Checking file status for: {}", path.display()); + if file_status.contains(STATUS_WT_DELETED) { + debug!("[GIT DELETE HOOK]: File is deleted: {}", path.display()); + 0 + } else { + debug!("[GIT DELETE HOOK]: Ignoring file: {}", path.display()); + 1 + } + }; + + try!( + index.add_all(&[id.local()], ADD_DEFAULT, Some(cb as &mut IndexMatchedPath)) + .map_err_into(GHEK::RepositoryPathAddingError) + .map_dbg_err_str("Failed to add to index") + .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_into_hook_error() + ); + + 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_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_into_hook_error() + ); + + let message = try!(commit_message(&repo, cfg, action) + .map_dbg_err_str("Failed to get commit message")); + + try!(repo.commit(Some("HEAD"), &signature, &signature, &message, &tree, &parents) + .map_dbg_str("Committed") + .map_dbg_err_str("Failed to commit") + .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_into_hook_error() + .map(|_| ()) } }