Merge pull request #951 from matthiasbeyer/remove-hooks

libimagstore: Remove hook support
This commit is contained in:
Matthias Beyer 2017-06-07 11:45:02 +02:00 committed by GitHub
commit bb9ff5bfd8
41 changed files with 340 additions and 4122 deletions

View file

@ -60,9 +60,6 @@ path = "../libimagrt"
[dependencies.libimagstore]
path = "../libimagstore"
[dependencies.libimagstorestdhook]
path = "../libimagstorestdhook"
[dependencies.libimagtimeui]
path = "../libimagtimeui"

View file

@ -184,7 +184,6 @@ from the others by `"/`". See below:
| part/lib/imagnotes | Targets library: imagnotes | [search][search-part/lib/imagnotes] |
| part/lib/imagrt | Targets library: imagrt | [search][search-part/lib/imagrt] |
| part/lib/imagstore | Targets library: imagstore | [search][search-part/lib/imagstore] |
| part/lib/imagstorestdhook | Targets library: imagstorestdhook | [search][search-part/lib/imagstorestdhook] |
| part/lib/imagutil | Targets library: | [search][search-part/lib/imagutil] |
| part/_new_binary | Introduces new binary | [search][search-part/_new_binary] |
| part/_new_library | Introduces new library | [search][search-part/_new_library] |
@ -283,7 +282,6 @@ _to be written_
[search-part/lib/imagnotes]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagnotes
[search-part/lib/imagrt]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagrt
[search-part/lib/imagstore]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagstore
[search-part/lib/imagstorestdhook]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagstorestdhook
[search-part/lib/imagutil]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagutil
[search-part/_new_binary]: https://github.com/matthiasbeyer/imag/labels/part%2F_new_binary
[search-part/_new_library]: https://github.com/matthiasbeyer/imag/labels/part%2F_new_library

View file

@ -31,7 +31,6 @@ members = [
"libimagref",
"libimagrt",
"libimagstore",
"libimagstorestdhook",
"libimagtimeui",
"libimagtodo",
"libimagutil",

View file

@ -12,5 +12,4 @@ to live in a imag binary.
### Long-term TODO
- [ ] Merge with `libimagstore`
- [ ] Merge with `libimagstorestdhook`

View file

@ -1,17 +0,0 @@
## libimagstorestdhook
The `libimagstorestdhook` is a library for default store hooks which are shipped
with the imag store.
Hooks are actions which can be performed before and after certain store actions,
for example before a file is created, or after a file is removed.
### Long-term TODO
- [ ] Merge with `libimagrt`
- [ ] Merge with `libimagstorestdhook`
- [ ] Create Runtime-wide "Store meta data" storage in the Runtime, which can be
set by users during the runtime of imag and then used by the hooks to get meta
information about their own runtime.
- [ ] Implement parallel store hook execution
- [ ] Implement Non-Mutable store hook execution

View file

@ -3,8 +3,7 @@
The store is the heart of everything. Here lives the data, the complexity and
the performance bottleneck.
The store offeres read/write access to all entries, a hook system to do
on-the-fly modification of incoming/outgoing files and so on.
The store offeres read/write access to all entries.
The store itself does not offer functionality, but has a commandline interface
"imag-store" which can do basic things with the store.
@ -13,5 +12,4 @@ The store itself does not offer functionality, but has a commandline interface
### Long-term TODO
- [ ] Merge with `libimagrt`
- [ ] Merge with `libimagstorestdhook`

View file

@ -33,148 +33,3 @@ readline_prompt = ">> "
# lives implicitely
implicit-create = false
# Hooks which get executed right before the Store is closed.
# They get the store path as StoreId passed, so they can alter the complete
# store, so these hooks should be chosen carefully.
store-unload-hook-aspects = [ "debug", "vcs" ]
pre-create-hook-aspects = [ "debug", "vcs" ]
post-create-hook-aspects = [ "debug", "vcs" ]
pre-move-hook-aspects = [ "debug" ]
post-move-hook-aspects = [ "debug" ]
pre-retrieve-hook-aspects = [ "debug", "vcs" ]
post-retrieve-hook-aspects = [ "debug", "vcs" ]
pre-update-hook-aspects = [ "debug", "vcs" ]
post-update-hook-aspects = [ "debug", "vcs" ]
pre-delete-hook-aspects = [ "debug", "vcs" ]
post-delete-hook-aspects = [ "debug", "vcs" ]
[store.aspects.debug]
parallel = false
mutable_hooks = true
[store.aspects.vcs]
parallel = false
mutable_hooks = false
[store.hooks.stdhook_debug]
aspect = "debug"
[store.hooks.stdhook_git_update]
aspect = "vcs"
# set to false to disable
enabled = true
# 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 = "refs/heads/master"
# Try to checkout the ensure_branch if it isn't checked out
try_checkout_ensure_branch = true
# Commit configuration
[store.hooks.stdhook_git_update.commit]
# Enable committing here. If not enabled, the "stdhook_git_storeunload" hook
# will commit all changes in one commit when the store is closed.
enabled = false
# 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 = "Update"
[store.hooks.stdhook_git_delete]
aspect = "vcs"
# set to false to disable
enabled = true
# 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 = "refs/heads/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]
# Enable committing here. If not enabled, the "stdhook_git_storeunload" hook
# will commit all changes in one commit when the store is closed.
enabled = false
# 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"
[store.hooks.stdhook_git_storeunload]
aspect = "vcs"
# set to false to disable
enabled = true
# 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 = "refs/heads/master"
# Try to checkout the ensure_branch if it isn't checked out
try_checkout_ensure_branch = true
# Commit configuration
[store.hooks.stdhook_git_storeunload.commit]
# Enable on-unload-committing, causing the store-unload hook to commit the
# changes to the store. This has no effect if the changes were already committed
# by the other git hooks.
enabled = true
# Do a git-add on all files that are not in the index yet, before committing.
# This must be turned on, as we do not support adding with "Update" hooks and
# only committing with the "Drop" hook, yet.
# So, effectively, disabling this will disable committing.
#
# If not set: false
add_wt_changes = true
# 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 = "Commit on drop"

View file

@ -585,6 +585,265 @@ fn process_rw_result(links: StoreResult<Option<Value>>) -> Result<LinkIter> {
Ok(LinkIter::new(links))
}
pub mod store_check {
use libimagstore::store::Store;
pub mod error {
generate_error_imports!();
use libimagstore::storeid::StoreId;
#[derive(Debug)]
pub enum StoreLinkConsistencyErrorCustomData {
DeadLink {
target: StoreId
},
OneDirectionalLink {
source: StoreId,
target: StoreId
},
}
impl Display for StoreLinkConsistencyErrorCustomData {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
use self::StoreLinkConsistencyErrorCustomData as SLCECD;
match self {
&SLCECD::DeadLink { ref target } => {
try!(write!(fmt, "Dead Link to '{}'", target))
},
&SLCECD::OneDirectionalLink { ref source, ref target } => {
try!(write!(fmt,
"Link from '{}' to '{}' does exist, but not other way round",
source, target))
}
};
Ok(())
}
}
generate_custom_error_types!(
StoreLinkConsistencyError,
StoreLinkConsistencyErrorKind,
StoreLinkConsistencyErrorCustomData,
StoreLinkConsistencyError => "Links in the store are not consistent",
LinkHandlingError => "Error in link handling",
StoreError => "Error while talking to the store"
);
generate_result_helper!(StoreLinkConsistencyError, StoreLinkConsistencyErrorKind);
generate_option_helper!(StoreLinkConsistencyError, StoreLinkConsistencyErrorKind);
}
pub use self::error::StoreLinkConsistencyError;
pub use self::error::StoreLinkConsistencyErrorKind;
pub use self::error::MapErrInto;
pub mod result {
use std::result::Result as RResult;
use internal::store_check::error::StoreLinkConsistencyError as SLCE;
pub type Result<T> = RResult<T, SLCE>;
}
use self::result::Result;
pub trait StoreLinkConsistentExt {
fn check_link_consistency(&self) -> Result<()>;
}
impl StoreLinkConsistentExt for Store {
fn check_link_consistency(&self) -> Result<()> {
use std::collections::HashMap;
use self::error::StoreLinkConsistencyErrorKind as SLCEK;
use self::error::StoreLinkConsistencyError as SLCE;
use self::error::StoreLinkConsistencyErrorCustomData as SLCECD;
use error::LinkErrorKind as LEK;
use result::Result as LResult;
use internal::InternalLinker;
use libimagstore::store::StoreObject;
use libimagstore::storeid::StoreId;
use libimagerror::iter::TraceIterator;
use libimagerror::into::IntoError;
use libimagutil::iter::FoldResult;
// Helper data structure to collect incoming and outgoing links for each StoreId
#[derive(Debug, Default)]
struct Linking {
outgoing: Vec<StoreId>,
incoming: Vec<StoreId>,
}
/// Helper function to aggregate the Link network
///
/// This function aggregates a HashMap which maps each StoreId object in the store onto
/// a Linking object, which contains a list of StoreIds which this entry links to and a
/// list of StoreIds which link to the current one.
///
/// The lambda returns an error if something fails
let aggregate_link_network = |store: &Store| -> Result<HashMap<StoreId, Linking>> {
store
.walk("") // this is a hack... I know...
.filter_map(|obj: StoreObject| match obj {
StoreObject::Id(id) => Some(id),
_ => None
}) // Only ids are interesting
.fold(Ok(HashMap::new()), |acc, sid| {
acc.and_then(|mut state| {
debug!("Checking entry: '{}'", sid);
match try!(self.get(sid).map_err_into(SLCEK::StoreError)) {
Some(fle) => {
debug!("Found FileLockEntry");
let fle_loc = fle.get_location();
let internal_links = fle
.get_internal_links()
.map_err_into(SLCEK::StoreError)?
.into_getter(self) // get the FLEs from the Store
.trace_unwrap(); // trace all Err(e)s and get the Ok(fle)s
for internal_link in internal_links {
let il_loc = internal_link.get_location();
state
.entry(il_loc.clone())
.or_insert(Linking::default())
.incoming
.push(fle_loc.clone());
// Make sure an empty linking object is present for the
// current StoreId object
state
.entry(fle_loc.clone())
.or_insert(Linking::default())
.outgoing
.push(il_loc.clone());
}
Ok(state)
},
None => {
debug!("No entry");
Ok(state)
}
}
})
})
};
/// Helper to check whethre all StoreIds in the network actually exists
///
/// Because why not?
let all_collected_storeids_exist = |network: &HashMap<StoreId, Linking>| -> LResult<()> {
network
.iter()
.fold_result(|(id, _)| {
if is_match!(self.get(id.clone()), Ok(Some(_))) {
debug!("Exists in store: {:?}", id);
let exists = {
use error::MapErrInto as MEI;
try!(MEI::map_err_into(id.exists(), LEK::StoreReadError))
};
if !exists {
warn!("Does exist in store but not on FS: {:?}", id);
Err(LEK::LinkTargetDoesNotExist.into_error())
} else {
Ok(())
}
} else {
warn!("Does not exist in store: {:?}", id);
Err(LEK::LinkTargetDoesNotExist.into_error())
}
})
};
/// Helper function to create a SLCECD::OneDirectionalLink error object
#[inline]
let mk_one_directional_link_err = |src: StoreId, target: StoreId| -> SLCE {
// construct the error
let custom = SLCECD::OneDirectionalLink {
source: src,
target: target,
};
SLCEK::StoreLinkConsistencyError
.into_error()
.with_custom_data(custom)
};
/// Helper lambda to check whether the _incoming_ links of each entry actually also
/// appear in the _outgoing_ list of the linked entry
let incoming_links_exists_as_outgoing_links =
|src: &StoreId, linking: &Linking, network: &HashMap<StoreId, Linking>| -> Result<()> {
linking
.incoming
.iter()
.fold_result(|link| {
// Check whether the links which are _incoming_ on _src_ are outgoing
// in each of the links in the incoming list.
let incoming_consistent = network.get(link)
.map(|l| l.outgoing.contains(src))
.unwrap_or(false);
if !incoming_consistent {
Err(mk_one_directional_link_err(src.clone(), link.clone()))
} else {
Ok(())
}
})
};
/// Helper lambda to check whether the _outgoing links of each entry actually also
/// appear in the _incoming_ list of the linked entry
let outgoing_links_exist_as_incoming_links =
|src: &StoreId, linking: &Linking, network: &HashMap<StoreId, Linking>| -> Result<()> {
linking
.outgoing
.iter()
.fold_result(|link| {
// Check whether the links which are _outgoing_ on _src_ are incoming
// in each of the links in the outgoing list.
let outgoing_consistent = network.get(link)
.map(|l| l.incoming.contains(src))
.unwrap_or(false);
if !outgoing_consistent {
Err(mk_one_directional_link_err(link.clone(), src.clone()))
} else {
Ok(())
}
})
};
aggregate_link_network(&self)
.and_then(|nw| {
all_collected_storeids_exist(&nw)
.map(|_| nw)
.map_err_into(SLCEK::LinkHandlingError)
})
.and_then(|nw| {
nw.iter().fold_result(|(id, linking)| {
try!(incoming_links_exists_as_outgoing_links(id, linking, &nw));
try!(outgoing_links_exist_as_incoming_links(id, linking, &nw));
Ok(())
})
})
.map(|_| ())
}
}
}
#[cfg(test)]
mod test {
use std::path::PathBuf;

View file

@ -118,7 +118,10 @@ macro_rules! generate_custom_error_types {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "[{}]", self.err_type));
Ok(())
match self.custom_data {
Some(ref c) => write!(fmt, "{}", c),
None => Ok(()),
}
}
}
@ -215,6 +218,13 @@ macro_rules! generate_error_types {
) => {
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)]
pub struct SomeNotExistingTypeWithATypeNameNoOneWillEverChoose {}
impl Display for SomeNotExistingTypeWithATypeNameNoOneWillEverChoose {
fn fmt(&self, _: &mut Formatter) -> Result<(), FmtError> {
Ok(())
}
}
generate_custom_error_types!($name, $kindname,
SomeNotExistingTypeWithATypeNameNoOneWillEverChoose,
$($kind => $string),*);
@ -241,6 +251,12 @@ mod test {
pub othr: i64,
}
impl Display for CustomData {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
Ok(())
}
}
generate_error_imports!();
#[allow(dead_code)]

View file

@ -26,9 +26,6 @@ ansi_term = "0.9"
[dependencies.libimagstore]
path = "../libimagstore"
[dependencies.libimagstorestdhook]
path = "../libimagstorestdhook"
[dependencies.libimagutil]
path = "../libimagutil"

View file

@ -44,7 +44,6 @@ extern crate clap;
extern crate toml;
extern crate libimagstore;
extern crate libimagstorestdhook;
extern crate libimagutil;
#[macro_use] extern crate libimagerror;

View file

@ -61,15 +61,7 @@ impl<'a> Runtime<'a> {
use clap::Shell;
use libimagstore::hook::position::HookPosition as HP;
use libimagstore::hook::Hook;
use libimagstore::error::StoreErrorKind;
use libimagstorestdhook::debug::DebugHook;
use libimagstorestdhook::vcs::git::delete::DeleteHook as GitDeleteHook;
use libimagstorestdhook::vcs::git::update::UpdateHook as GitUpdateHook;
use libimagstorestdhook::vcs::git::store_unload::StoreUnloadHook as GitStoreUnloadHook;
use libimagerror::trace::trace_error;
use libimagerror::trace::trace_error_dbg;
use libimagerror::into::IntoError;
use configuration::error::ConfigErrorKind;
@ -142,52 +134,7 @@ impl<'a> Runtime<'a> {
write!(stderr(), "Store-config: {:?}\n", store_config).ok();
}
Store::new(storepath.clone(), store_config).map(|mut store| {
// If we are debugging, generate hooks for all positions
if is_debugging {
let hooks : Vec<(Box<Hook>, &str, HP)> = vec![
(Box::new(DebugHook::new(HP::PreCreate)) , "debug", HP::PreCreate),
(Box::new(DebugHook::new(HP::PostCreate)) , "debug", HP::PostCreate),
(Box::new(DebugHook::new(HP::PreRetrieve)) , "debug", HP::PreRetrieve),
(Box::new(DebugHook::new(HP::PostRetrieve)) , "debug", HP::PostRetrieve),
(Box::new(DebugHook::new(HP::PreUpdate)) , "debug", HP::PreUpdate),
(Box::new(DebugHook::new(HP::PostUpdate)) , "debug", HP::PostUpdate),
(Box::new(DebugHook::new(HP::PreDelete)) , "debug", HP::PreDelete),
(Box::new(DebugHook::new(HP::PostDelete)) , "debug", HP::PostDelete),
];
// If hook registration fails, trace the error and warn, but continue.
for (hook, aspectname, position) in hooks {
if let Err(e) = store.register_hook(position, &String::from(aspectname), hook) {
if e.err_type() == StoreErrorKind::HookRegisterError {
trace_error_dbg(&e);
warn!("Registering debug hook with store failed");
} else {
trace_error(&e);
};
}
}
}
let sp = storepath;
let hooks : Vec<(Box<Hook>, &str, HP)> = vec![
(Box::new(GitDeleteHook::new(sp.clone(), HP::PostDelete)), "vcs", HP::PostDelete),
(Box::new(GitUpdateHook::new(sp.clone(), HP::PostUpdate)), "vcs", HP::PostUpdate),
(Box::new(GitStoreUnloadHook::new(sp)), "vcs", HP::StoreUnload),
];
for (hook, aspectname, position) in hooks {
if let Err(e) = store.register_hook(position, &String::from(aspectname), hook) {
if e.err_type() == StoreErrorKind::HookRegisterError {
trace_error_dbg(&e);
warn!("Registering git hook with store failed");
} else {
trace_error(&e);
};
}
}
Store::new(storepath.clone(), store_config).map(|store| {
Runtime {
cli_matches: matches,
configuration: cfg,

View file

@ -66,3 +66,8 @@ verify = []
#
early-panic=[]
# File system locking
#
# Enable this feature to enable file-system locking in the store.
fs-locking = []

View file

@ -20,160 +20,19 @@
use toml::Value;
use libimagerror::into::IntoError;
use libimagutil::iter::FoldResult;
use store::Result;
/// Check whether the configuration is valid for the store
///
/// The passed `Value` _must be_ the `[store]` sub-tree of the configuration. Otherwise this will
/// fail.
///
/// It checks whether the configuration looks like the store wants it to be:
///
/// ```toml
/// [store]
/// pre-create-hook-aspects = [ "misc", "encryption", "version-control"]
///
/// [store.aspects.misc]
/// parallel = true
///
/// [store.aspects.encryption]
/// parallel = false
///
/// [store.aspects.version-control]
/// parallel = false
///
/// [store.hooks.gnupg]
/// aspect = "encryption"
/// key = "0x123456789"
///
/// [store.hooks.git]
/// aspect = "version-control"
/// ```
///
/// It checks:
/// * Whether all the maps are there (whether store, store.aspects, store.aspects.example are all
/// maps)
/// * Whether each aspect configuration has a "parallel = <Boolean>" setting
/// * Whether each hook congfiguration has a "aspect = <String>" setting
///
/// It does NOT check:
/// * Whether all aspects which are used in the hook configuration are also configured
///
/// No configuration is a valid configuration, as the store will use the most conservative settings
/// automatically. This has also performance impact, as all hooks run in no-parallel mode then.
/// You have been warned!
///
///
pub fn config_is_valid(config: &Option<Value>) -> Result<()> {
use std::collections::BTreeMap;
use error::StoreErrorKind as SEK;
if config.is_none() {
return Ok(());
}
/// Check whether the config has a key with a string array.
/// The `key` is the key which is checked
/// The `kind` is the error kind which is used as `cause` if there is an error, so we can
/// indicate via error type which key is missing
fn has_key_with_string_ary(v: &BTreeMap<String, Value>, key: &str,
kind: SEK) -> Result<()> {
v.get(key)
.ok_or_else(|| {
warn!("Required key '{}' is not in store config", key);
SEK::ConfigKeyMissingError.into_error_with_cause(Box::new(kind.into_error()))
})
.and_then(|t| match *t {
Value::Array(ref a) => {
a.iter().fold_result(|elem| if is_match!(*elem, Value::String(_)) {
Ok(())
} else {
let cause = Box::new(kind.into_error());
Err(SEK::ConfigTypeError.into_error_with_cause(cause))
})
},
_ => {
warn!("Key '{}' in store config should contain an array", key);
Err(SEK::ConfigTypeError.into_error_with_cause(Box::new(kind.into_error())))
}
})
}
/// Check that
/// * the top-level configuration
/// * is a table
/// * where all entries of a key `section` (eg. "hooks" or "aspects")
/// * Are maps
/// * where each has a key `key` (eg. "aspect" or "parallel")
/// * which fullfills constraint `f` (typecheck)
fn check_all_inner_maps_have_key_with<F>(store_config: &BTreeMap<String, Value>,
section: &str,
key: &str,
f: F)
-> Result<()>
where F: Fn(&Value) -> bool
{
store_config.get(section) // The store config has the section `section`
.ok_or_else(|| {
warn!("Store config expects section '{}' to be present, but isn't.", section);
SEK::ConfigKeyMissingError.into_error()
})
.and_then(|section_table| match *section_table { // which is
Value::Table(ref section_table) => // a table
section_table.iter().fold_result(|(inner_key, cfg)| {
match *cfg {
Value::Table(ref hook_config) => { // are tables
// with a key
let hook_aspect_is_valid = try!(hook_config.get(key)
.map(|hook_aspect| f(&hook_aspect))
.ok_or(SEK::ConfigKeyMissingError.into_error())
);
if !hook_aspect_is_valid {
Err(SEK::ConfigTypeError.into_error())
} else {
Ok(())
}
},
_ => {
warn!("Store config expects '{}' to be in '{}.{}', but isn't.",
key, section, inner_key);
Err(SEK::ConfigKeyMissingError.into_error())
}
}
}),
_ => {
warn!("Store config expects '{}' to be a Table, but isn't.", section);
Err(SEK::ConfigTypeError.into_error())
}
})
}
match *config {
Some(Value::Table(ref t)) => {
try!(has_key_with_string_ary(t, "store-unload-hook-aspects", SEK::ConfigKeyUnloadAspectsError));
try!(has_key_with_string_ary(t, "pre-create-hook-aspects", SEK::ConfigKeyPreCreateAspectsError));
try!(has_key_with_string_ary(t, "post-create-hook-aspects", SEK::ConfigKeyPostCreateAspectsError));
try!(has_key_with_string_ary(t, "pre-retrieve-hook-aspects", SEK::ConfigKeyPreRetrieveAspectsError));
try!(has_key_with_string_ary(t, "post-retrieve-hook-aspects", SEK::ConfigKeyPostRetrieveAspectsError));
try!(has_key_with_string_ary(t, "pre-update-hook-aspects", SEK::ConfigKeyPreUpdateAspectsError));
try!(has_key_with_string_ary(t, "post-update-hook-aspects", SEK::ConfigKeyPostUpdateAspectsError));
try!(has_key_with_string_ary(t, "pre-delete-hook-aspects", SEK::ConfigKeyPreDeleteAspectsError));
try!(has_key_with_string_ary(t, "post-delete-hook-aspects", SEK::ConfigKeyPostDeleteAspectsError));
// The section "hooks" has maps which have a key "aspect" which has a value of type
// String
try!(check_all_inner_maps_have_key_with(t, "hooks", "aspect",
|asp| is_match!(asp, &Value::String(_))));
// The section "aspects" has maps which have a key "parllel" which has a value of type
// Boolean
check_all_inner_maps_have_key_with(t, "aspects", "parallel",
|asp| is_match!(asp, &Value::Boolean(_)))
}
Some(Value::Table(_)) => Ok(()),
_ => {
warn!("Store config is no table");
Err(SEK::ConfigTypeError.into_error())
@ -207,152 +66,6 @@ pub fn config_implicit_store_create_allowed(config: Option<&Value>) -> bool {
}).unwrap_or(false)
}
pub fn get_store_unload_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("store-unload-hook-aspects", value)
}
pub fn get_pre_create_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("pre-create-hook-aspects", value)
}
pub fn get_post_create_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("post-create-hook-aspects", value)
}
pub fn get_pre_retrieve_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("pre-retrieve-hook-aspects", value)
}
pub fn get_post_retrieve_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("post-retrieve-hook-aspects", value)
}
pub fn get_pre_update_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("pre-update-hook-aspects", value)
}
pub fn get_post_update_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("post-update-hook-aspects", value)
}
pub fn get_pre_delete_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("pre-delete-hook-aspects", value)
}
pub fn get_post_delete_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("post-delete-hook-aspects", value)
}
pub fn get_pre_move_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("pre-move-hook-aspects", value)
}
pub fn get_post_move_aspect_names(value: &Option<Value>) -> Vec<String> {
get_aspect_names_for_aspect_position("post-move-hook-aspects", value)
}
#[derive(Debug)]
pub struct AspectConfig {
parallel: bool,
mutable_hooks: bool,
config: Value,
}
impl AspectConfig {
pub fn new(init: Value) -> AspectConfig {
debug!("Trying to parse AspectConfig from: {:?}", init);
let parallel = AspectConfig::is_parallel(&init);
let muthooks = AspectConfig::allows_mutable_hooks(&init);
AspectConfig {
config: init,
mutable_hooks: muthooks,
parallel: parallel,
}
}
fn is_parallel(init: &Value) -> bool {
match *init {
Value::Table(ref t) =>
t.get("parallel")
.map_or(false, |value| {
match *value {
Value::Boolean(b) => b,
_ => false,
}
}),
_ => false,
}
}
fn allows_mutable_hooks(init: &Value) -> bool {
match *init {
Value::Table(ref t) =>
t.get("mutable_hooks")
.map_or(false, |value| {
match *value {
Value::Boolean(b) => b,
_ => false,
}
}),
_ => false,
}
}
pub fn allow_mutable_hooks(&self) -> bool {
self.mutable_hooks
}
/// Get the aspect configuration for an aspect.
///
/// Pass the store configuration object, this searches in `[aspects][<aspect_name>]`.
///
/// Returns `None` if one of the keys in the chain is not available
pub fn get_for(v: &Option<Value>, a_name: String) -> Option<AspectConfig> {
debug!("Get aspect configuration for {:?} from {:?}", a_name, v);
let res = match *v {
Some(Value::Table(ref tabl)) => {
match tabl.get("aspects") {
Some(&Value::Table(ref tabl)) => {
tabl.get(&a_name[..]).map(|asp| AspectConfig::new(asp.clone()))
},
_ => None,
}
},
_ => None,
};
debug!("Found aspect configuration for {:?}: {:?}", a_name, res);
res
}
}
fn get_aspect_names_for_aspect_position(config_name: &'static str, value: &Option<Value>) -> Vec<String> {
use itertools::Itertools;
let mut v = vec![];
match *value {
Some(Value::Table(ref t)) => {
match t.get(config_name) {
Some(&Value::Array(ref a)) => {
for elem in a {
match *elem {
Value::String(ref s) => v.push(s.clone()),
_ => warn!("Non-String in configuration, inside '{}'", config_name),
}
}
},
_ => warn!("'{}' configuration key should contain Array, does not", config_name),
};
},
None => warn!("No store configuration, cannot get '{}'", config_name),
_ => warn!("Configuration is not a table"),
}
v.into_iter().unique().collect()
}
#[cfg(test)]
mod tests {
use toml::de::from_str as toml_from_str;
@ -387,312 +100,5 @@ mod tests {
assert!(config_implicit_store_create_allowed(Some(config).as_ref()));
}
#[test]
fn test_get_store_unload_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
let names = get_store_unload_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_store_unload_aspect_names_empty() {
let config = toml_from_str(r#"
store-unload-hook-aspects = [ ]
"#).unwrap();
let names = get_store_unload_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_store_unload_aspect_names_one_elem() {
let config = toml_from_str(r#"
store-unload-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_store_unload_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_pre_create_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_pre_create_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_pre_create_aspect_names_empty() {
let config = toml_from_str(r#"
pre-create-hook-aspects = [ ]
"#).unwrap();
let names = get_pre_create_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_pre_create_aspect_names_one_elem() {
let config = toml_from_str(r#"
pre-create-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_pre_create_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_post_create_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_post_create_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_post_create_aspect_names_empty() {
let config = toml_from_str(r#"
post-create-hook-aspects = [ ]
"#).unwrap();
let names = get_post_create_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_post_create_aspect_names_one_elem() {
let config = toml_from_str(r#"
post-create-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_post_create_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_pre_retrieve_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_pre_retrieve_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_pre_retrieve_aspect_names_empty() {
let config = toml_from_str(r#"
pre-retrieve-hook-aspects = [ ]
"#).unwrap();
let names = get_pre_retrieve_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_pre_retrieve_aspect_names_one_elem() {
let config = toml_from_str(r#"
pre-retrieve-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_pre_retrieve_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_post_retrieve_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_post_retrieve_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_post_retrieve_aspect_names_empty() {
let config = toml_from_str(r#"
post-retrieve-hook-aspects = [ ]
"#).unwrap();
let names = get_post_retrieve_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_post_retrieve_aspect_names_one_elem() {
let config = toml_from_str(r#"
post-retrieve-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_post_retrieve_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_pre_update_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_pre_update_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_pre_update_aspect_names_empty() {
let config = toml_from_str(r#"
pre-update-hook-aspects = [ ]
"#).unwrap();
let names = get_pre_update_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_pre_update_aspect_names_one_elem() {
let config = toml_from_str(r#"
pre-update-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_pre_update_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_post_update_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_post_update_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_post_update_aspect_names_empty() {
let config = toml_from_str(r#"
post-update-hook-aspects = [ ]
"#).unwrap();
let names = get_post_update_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_post_update_aspect_names_one_elem() {
let config = toml_from_str(r#"
post-update-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_post_update_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_pre_delete_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_pre_delete_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_pre_delete_aspect_names_empty() {
let config = toml_from_str(r#"
pre-delete-hook-aspects = [ ]
"#).unwrap();
let names = get_pre_delete_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_pre_delete_aspect_names_one_elem() {
let config = toml_from_str(r#"
pre-delete-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_pre_delete_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_post_delete_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_post_delete_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_post_delete_aspect_names_empty() {
let config = toml_from_str(r#"
post-delete-hook-aspects = [ ]
"#).unwrap();
let names = get_post_delete_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_post_delete_aspect_names_one_elem() {
let config = toml_from_str(r#"
post-delete-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_post_delete_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_pre_move_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_pre_move_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_pre_move_aspect_names_empty() {
let config = toml_from_str(r#"
pre-move-hook-aspects = [ ]
"#).unwrap();
let names = get_pre_move_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_pre_move_aspect_names_one_elem() {
let config = toml_from_str(r#"
pre-move-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_pre_move_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_post_move_aspect_names_not_existent() {
let config = toml_from_str("").unwrap();
assert!(get_post_move_aspect_names(&Some(config)).is_empty());
}
#[test]
fn test_get_post_move_aspect_names_empty() {
let config = toml_from_str(r#"
post-move-hook-aspects = [ ]
"#).unwrap();
let names = get_post_move_aspect_names(&Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_post_move_aspect_names_one_elem() {
let config = toml_from_str(r#"
post-move-hook-aspects = [ "example" ]
"#).unwrap();
let names = get_post_move_aspect_names(&Some(config));
assert_eq!(1, names.len());
assert_eq!("example", names.iter().next().unwrap());
}
#[test]
fn test_get_aspect_names_for_aspect_position_arbitrary_empty() {
let config = toml_from_str(r#"
test-key = [ ]
"#).unwrap();
let names = get_aspect_names_for_aspect_position("test-key", &Some(config));
assert!(names.is_empty());
}
#[test]
fn test_get_aspect_names_for_aspect_position_arbitrary_one() {
let config = toml_from_str(r#"
test-key = [ "test-value" ]
"#).unwrap();
let names = get_aspect_names_for_aspect_position("test-key", &Some(config));
assert_eq!(1, names.len());
assert_eq!("test-value", names.iter().next().unwrap());
}
#[test]
fn test_get_aspect_names_for_aspect_position_arbitrary_duplicated() {
let config = toml_from_str(r#"
test-key = [ "test-value", "test-value" ]
"#).unwrap();
let names = get_aspect_names_for_aspect_position("test-key", &Some(config));
assert_eq!(1, names.len());
let mut iter = names.iter();
assert_eq!("test-value", iter.next().unwrap());
assert!(iter.next().is_none());
}
}

View file

@ -23,21 +23,17 @@ use std::convert::From;
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)]
pub struct CustomErrorData {}
impl Display for CustomErrorData {
fn fmt(&self, _: &mut Formatter) -> Result<(), FmtError> {
Ok(()) // Do nothing here, we don't need to print smth
}
}
generate_custom_error_types!(StoreError, StoreErrorKind, CustomErrorData,
ConfigurationError => "Store Configuration Error",
ConfigTypeError => "Store configuration type error",
ConfigKeyMissingError => "Configuration Key missing",
ConfigKeyUnloadAspectsError => "Config Key 'store-unload-hook-aspects' caused an error",
ConfigKeyPreCreateAspectsError => "Config Key 'pre-create-hook-aspects' caused an error",
ConfigKeyPostCreateAspectsError => "Config Key 'post-create-hook-aspects' caused an error",
ConfigKeyPreRetrieveAspectsError => "Config Key 'pre-retrieve-hook-aspect' caused an error",
ConfigKeyPostRetrieveAspectsError => "Config Key 'post-retrieve-hook-aspec' caused an error",
ConfigKeyPreUpdateAspectsError => "Config Key 'pre-update-hook-aspects' caused an error",
ConfigKeyPostUpdateAspectsError => "Config Key 'post-update-hook-aspects' caused an error",
ConfigKeyPreDeleteAspectsError => "Config Key 'pre-delete-hook-aspects' caused an error",
ConfigKeyPostDeleteAspectsError => "Config Key 'post-delete-hook-aspects' caused an error",
CreateStoreDirDenied => "Creating store directory implicitely denied",
FileError => "File Error",
IoError => "IO Error",
@ -63,11 +59,6 @@ generate_custom_error_types!(StoreError, StoreErrorKind, CustomErrorData,
HeaderPathTypeFailure => "Header has wrong type for path",
HeaderKeyNotFound => "Header Key not found",
HeaderTypeFailure => "Header type is wrong",
HookRegisterError => "Hook register error",
AspectNameNotFoundError => "Aspect name not found",
HookExecutionError => "Hook execution error",
PreHookExecuteError => "Pre-Hook execution error",
PostHookExecuteError => "Post-Hook execution error",
StorePathLacksVersion => "The supplied store path has no version part",
GlobError => "glob() error",
EncodingError => "Encoding error",

View file

@ -1,49 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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::fmt::Debug;
use hook::result::HookResult;
use store::FileLockEntry;
use storeid::StoreId;
pub trait StoreIdAccessor : Debug + Send {
fn access(&self, &StoreId) -> HookResult<()>;
}
pub trait MutableHookDataAccessor : Debug + Send {
fn access_mut(&self, &mut FileLockEntry) -> HookResult<()>;
}
pub trait NonMutableHookDataAccessor : Debug + Send {
fn access(&self, &FileLockEntry) -> HookResult<()>;
}
#[derive(Debug)]
pub enum HookDataAccessor<'a> {
StoreIdAccess(&'a StoreIdAccessor),
MutableAccess(&'a MutableHookDataAccessor),
NonMutableAccess(&'a NonMutableHookDataAccessor),
}
pub trait HookDataAccessorProvider {
fn accessor(&self) -> HookDataAccessor;
}

View file

@ -1,151 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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 libimagerror::trace::trace_error;
use libimagutil::iter::FoldResult;
use store::FileLockEntry;
use storeid::StoreId;
use hook::Hook;
use hook::result::HookResult;
use hook::accessor::{StoreIdAccessor, MutableHookDataAccessor, NonMutableHookDataAccessor};
use hook::accessor::HookDataAccessor as HDA;
use hook::error::HookError as HE;
use hook::error::HookErrorKind as HEK;
use configuration::AspectConfig;
#[derive(Debug)]
pub struct Aspect {
cfg: Option<AspectConfig>,
name: String,
hooks: Vec<Box<Hook>>,
}
impl Aspect {
pub fn new(name: String, cfg: Option<AspectConfig>) -> Aspect {
Aspect {
cfg: cfg,
name: name,
hooks: vec![],
}
}
pub fn name(&self) -> &String {
&self.name
}
pub fn register_hook(&mut self, h: Box<Hook>) {
self.hooks.push(h);
}
}
impl StoreIdAccessor for Aspect {
fn access(&self, id: &StoreId) -> HookResult<()> {
let accessors : Vec<HDA> = self.hooks.iter().map(|h| h.accessor()).collect();
if !accessors.iter().all(|a| {
let x = is_match!(*a, HDA::StoreIdAccess(_));
if !x {
warn!("Denied execution of None-StoreId-Accessing Hook");
debug!("Accessor: {:?}", a);
debug!("in StoreIdAccess-Aspect execution: {:?}", self);
}
x
}) {
return Err(HE::new(HEK::AccessTypeViolation, None));
}
accessors.iter().fold_result(|accessor| {
let res = match accessor {
&HDA::StoreIdAccess(accessor) => accessor.access(id),
_ => unreachable!(),
};
trace_hook_errors(res)
})
}
}
impl MutableHookDataAccessor for Aspect {
fn access_mut(&self, fle: &mut FileLockEntry) -> HookResult<()> {
debug!("Checking whether mutable hooks are allowed");
debug!("-> config = {:?}", self.cfg);
let accessors : Vec<HDA> = self.hooks.iter().map(|h| h.accessor()).collect();
// TODO: Naiive implementation.
// More sophisticated version would check whether there are _chunks_ of
// NonMutableAccess accessors and execute these chunks in parallel. We do not have
// performance concerns yet, so this is okay.
accessors.iter().fold_result(|accessor| {
let res = match accessor {
&HDA::StoreIdAccess(ref accessor) => accessor.access(fle.get_location()),
&HDA::NonMutableAccess(ref accessor) => accessor.access(fle),
&HDA::MutableAccess(ref accessor) => {
if !self.cfg.as_ref().map(|c| c.allow_mutable_hooks()).unwrap_or(false) {
debug!("Apparently mutable hooks are not allowed... failing now.");
return Err(HE::new(HEK::MutableHooksNotAllowed, None));
}
accessor.access_mut(fle)
},
};
trace_hook_errors(res)
})
}
}
impl NonMutableHookDataAccessor for Aspect {
fn access(&self, fle: &FileLockEntry) -> HookResult<()> {
let accessors : Vec<HDA> = self.hooks.iter().map(|h| h.accessor()).collect();
if !accessors.iter().all(|a| {
let x = is_match!(*a, HDA::NonMutableAccess(_));
if !x {
warn!("Denied execution of Non-Mutable-Accessing Hook");
debug!("Accessor: {:?}", a);
debug!("in StoreIdAccess-Aspect execution: {:?}", self);
}
x
}) {
return Err(HE::new(HEK::AccessTypeViolation, None));
}
accessors.iter().fold_result(|accessor| {
let res = match accessor {
&HDA::NonMutableAccess(accessor) => accessor.access(fle),
_ => unreachable!(),
};
trace_hook_errors(res)
})
}
}
fn trace_hook_errors(res: HookResult<()>) -> HookResult<()> {
res.or_else(|e| {
if !e.is_aborting() {
trace_error(&e);
// ignore error if it is not aborting, as we printed it already
Ok(())
} else {
Err(e)
}
})
}

View file

@ -1,65 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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::default::Default;
generate_error_imports!();
generate_custom_error_types!(HookError, HookErrorKind, CustomData,
HookExecutionError => "Hook exec error",
AccessTypeViolation => "Hook access type violation",
MutableHooksNotAllowed => "Mutable Hooks are denied"
);
generate_result_helper!(HookError, HookErrorKind);
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)]
pub struct CustomData {
aborting: bool,
}
impl CustomData {
pub fn aborting(mut self, b: bool) -> CustomData {
self.aborting = b;
self
}
}
impl Default for CustomData {
fn default() -> CustomData {
CustomData {
aborting: true
}
}
}
impl HookError {
pub fn is_aborting(&self) -> bool {
match self.custom_data {
Some(b) => b.aborting,
None => true
}
}
}

View file

@ -1,36 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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::fmt::Debug;
use toml::Value;
pub mod accessor;
pub mod aspect;
pub mod error;
pub mod position;
pub mod result;
use hook::accessor::HookDataAccessorProvider;
pub trait Hook : HookDataAccessorProvider + Debug + Send {
fn name(&self) -> &'static str;
fn set_config(&mut self, cfg: &Value);
}

View file

@ -1,32 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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
//
#[derive(Debug, Clone)]
pub enum HookPosition {
StoreUnload,
PreCreate,
PostCreate,
PreRetrieve,
PostRetrieve,
PreUpdate,
PostUpdate,
PreDelete,
PostDelete,
}

View file

@ -1,22 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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 hook::error::HookError;
pub type HookResult<T> = Result<T, HookError>;

View file

@ -52,7 +52,6 @@ extern crate libimagutil;
pub mod storeid;
pub mod error;
pub mod hook;
pub mod store;
mod configuration;
mod file_abstraction;

File diff suppressed because it is too large Load diff

View file

@ -1,36 +0,0 @@
[package]
name = "libimagstorestdhook"
version = "0.3.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
description = "Library for the imag core distribution"
keywords = ["imag", "PIM", "personal", "information", "management"]
readme = "../README.md"
license = "LGPL-2.1"
documentation = "https://matthiasbeyer.github.io/imag/imag_documentation/index.html"
repository = "https://github.com/matthiasbeyer/imag"
homepage = "http://imag-pim.org"
[dependencies]
toml = "^0.4"
log = "0.3"
fs2 = "0.3"
git2 = "0.5"
[dependencies.libimagstore]
path = "../libimagstore"
[dependencies.libimagentrylink]
path = "../libimagentrylink"
[dependencies.libimaginteraction]
path = "../libimaginteraction"
[dependencies.libimagerror]
path = "../libimagerror"
[dependencies.libimagutil]
path = "../libimagutil"

View file

@ -1 +0,0 @@
../doc/src/05100-lib-store-std-hook.md

View file

@ -1,134 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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 toml::Value;
use libimagstore::hook::Hook;
use libimagstore::hook::accessor::HookDataAccessor;
use libimagstore::hook::accessor::HookDataAccessorProvider;
use libimagstore::hook::position::HookPosition;
use self::accessor::DebugHookAccessor as DHA;
#[derive(Debug)]
pub struct DebugHook {
position: HookPosition,
accessor: DHA,
}
impl DebugHook {
pub fn new(pos: HookPosition) -> DebugHook {
DebugHook {
position: pos.clone(),
accessor: DHA::new(pos),
}
}
}
impl Hook for DebugHook {
fn name(&self) -> &'static str {
"stdhook_debug"
}
fn set_config(&mut self, c: &Value) {
debug!("Trying to set configuration in debug hook: {:?}", c);
debug!("Ignoring configuration in debug hook, we don't need a config here");
}
}
impl HookDataAccessorProvider for DebugHook {
fn accessor(&self) -> HookDataAccessor {
use libimagstore::hook::position::HookPosition as HP;
use libimagstore::hook::accessor::HookDataAccessor as HDA;
match self.position {
HP::StoreUnload |
HP::PreCreate |
HP::PreRetrieve |
HP::PreDelete |
HP::PostDelete => HDA::StoreIdAccess(&self.accessor),
HP::PostCreate |
HP::PostRetrieve |
HP::PreUpdate |
HP::PostUpdate => HDA::MutableAccess(&self.accessor),
}
}
}
pub mod accessor {
use std::ops::Deref;
use libimagstore::storeid::StoreId;
use libimagstore::store::FileLockEntry;
use libimagstore::hook::result::HookResult;
use libimagstore::hook::accessor::MutableHookDataAccessor;
use libimagstore::hook::accessor::NonMutableHookDataAccessor;
use libimagstore::hook::accessor::StoreIdAccessor;
use libimagstore::hook::position::HookPosition;
#[derive(Debug)]
pub struct DebugHookAccessor {
position: HookPosition,
}
impl DebugHookAccessor {
pub fn new(position: HookPosition) -> DebugHookAccessor {
DebugHookAccessor {
position: position,
}
}
}
impl StoreIdAccessor for DebugHookAccessor {
fn access(&self, id: &StoreId) -> HookResult<()> {
debug!("[DEBUG HOOK]: {:?}", id);
Ok(())
}
}
impl MutableHookDataAccessor for DebugHookAccessor {
fn access_mut(&self, fle: &mut FileLockEntry) -> HookResult<()> {
debug!("[DEBUG HOOK] {:?}", fle.deref().deref());
Ok(())
}
}
impl NonMutableHookDataAccessor for DebugHookAccessor {
fn access(&self, fle: &FileLockEntry) -> HookResult<()> {
debug!("[DEBUG HOOK] {:?}", fle.deref().deref());
Ok(())
}
}
}

View file

@ -1,119 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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 toml::Value;
use libimagstore::hook::Hook;
use libimagstore::hook::error::HookErrorKind as HEK;
use libimagstore::hook::accessor::HookDataAccessor as HDA;
use libimagstore::hook::accessor::HookDataAccessorProvider;
use libimagstore::hook::accessor::NonMutableHookDataAccessor;
use libimagstore::hook::result::HookResult;
use libimagstore::store::FileLockEntry;
use libimagstore::toml_ext::TomlValueExt;
use libimagentrylink::internal::InternalLinker;
use libimagerror::trace::trace_error;
mod error {
generate_error_imports!();
generate_error_types!(NoLinksLeftCheckerHookError, NoLinksLeftCheckerHookErrorKind,
ReadInternalLinksError => "Error while reading internal links of entry",
LinksLeft => "The entry has links and therefor cannot be deleted."
);
}
use self::error::NoLinksLeftCheckerHookErrorKind as NLLCHEK;
#[derive(Debug, Clone)]
pub struct DenyDeletionOfLinkedEntriesHook {
abort: bool
}
impl DenyDeletionOfLinkedEntriesHook {
pub fn new() -> DenyDeletionOfLinkedEntriesHook {
DenyDeletionOfLinkedEntriesHook {
abort: true // by default, this hook aborts actions
}
}
}
impl Hook for DenyDeletionOfLinkedEntriesHook {
fn name(&self) -> &'static str {
"stdhook_linked_entries_cannot_be_deleted"
}
fn set_config(&mut self, v: &Value) {
self.abort = match v.read("aborting") {
Ok(Some(Value::Boolean(b))) => b,
Ok(Some(_)) => {
warn!("Configuration error, 'aborting' must be a Boolean (true|false).");
warn!("Assuming 'true' now.");
true
},
Ok(None) => {
warn!("No key 'aborting' - Assuming 'true'");
true
},
Err(e) => {
error!("Error parsing TOML:");
trace_error(&e);
false
},
};
}
}
impl HookDataAccessorProvider for DenyDeletionOfLinkedEntriesHook {
fn accessor(&self) -> HDA {
HDA::NonMutableAccess(self)
}
}
impl NonMutableHookDataAccessor for DenyDeletionOfLinkedEntriesHook {
fn access(&self, fle: &FileLockEntry) -> HookResult<()> {
use libimagerror::into::IntoError;
use self::error::MapErrInto;
debug!("[NO LINKS LEFT CHECKER HOOK] {:?}", fle.get_location());
let n = try!(fle
.get_internal_links()
.map(|i| i.count())
.map_err_into(NLLCHEK::ReadInternalLinksError)
.map_err(Box::new)
.map_err(|e| HEK::HookExecutionError.into_error_with_cause(e)));
if n > 0 {
Err(NLLCHEK::LinksLeft.into_error())
.map_err(Box::new)
.map_err(|e| HEK::HookExecutionError.into_error_with_cause(e))
} else {
Ok(())
}
}
}

View file

@ -1,167 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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 toml::Value;
use fs2::FileExt;
use std::fs::File;
use libimagstore::hook::Hook;
use libimagstore::hook::accessor::HookDataAccessor as HDA;
use libimagstore::hook::accessor::HookDataAccessorProvider;
use libimagstore::hook::accessor::StoreIdAccessor;
use libimagstore::hook::accessor::MutableHookDataAccessor;
use libimagstore::hook::accessor::NonMutableHookDataAccessor;
use libimagstore::hook::result::HookResult;
use libimagstore::hook::error::{HookError, HookErrorKind};
use libimagstore::storeid::StoreId;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Entry;
mod error {
generate_error_imports!();
generate_error_types!(FlockError, FlockErrorKind,
IOError => "IO Error",
StoreIdPathBufConvertError => "Error while converting StoreId to PathBuf",
FileOpenError => "Error on File::open()",
LockError => "Error while lock()ing",
UnlockError => "Error while unlock()ing"
);
}
use self::error::FlockError as FE;
use self::error::FlockErrorKind as FEK;
use self::error::MapErrInto;
trait EntryFlock {
fn lock(&self) -> Result<(), FE>;
fn unlock(&self) -> Result<(), FE>;
}
fn open_file(id: StoreId) -> Result<File, FE> {
id.into_pathbuf()
.map_err_into(FEK::StoreIdPathBufConvertError)
.and_then(|loc| {
File::open(loc)
.map_err_into(FEK::FileOpenError)
.map_err_into(FEK::IOError)
})
}
impl EntryFlock for Entry {
fn lock(&self) -> Result<(), FE> {
open_file(self.get_location().clone())
.and_then(|file| {
file.lock_exclusive()
.map_err_into(FEK::LockError)
.map_err_into(FEK::IOError)
})
}
fn unlock(&self) -> Result<(), FE> {
open_file(self.get_location().clone())
.and_then(|file| {
file.unlock()
.map_err_into(FEK::UnlockError)
.map_err_into(FEK::LockError)
.map_err_into(FEK::IOError)
})
}
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Action {
Lock,
Unlock
}
fn action_to_str(a: &Action) -> &'static str {
match *a {
Action::Lock => "lock",
Action::Unlock => "unlock",
}
}
#[derive(Debug, Clone)]
pub struct FlockUpdateHook {
action: Action,
}
impl FlockUpdateHook {
pub fn new(action: Action) -> FlockUpdateHook {
FlockUpdateHook {
action: action,
}
}
}
impl Hook for FlockUpdateHook {
fn name(&self) -> &'static str {
"stdhook_flock_update"
}
fn set_config(&mut self, _: &Value) {
() // We are not configurable here.
}
}
impl HookDataAccessorProvider for FlockUpdateHook {
fn accessor(&self) -> HDA {
HDA::StoreIdAccess(self)
}
}
impl StoreIdAccessor for FlockUpdateHook {
fn access(&self, id: &StoreId) -> HookResult<()> {
debug!("[FLOCK HOOK][{}] {:?}", action_to_str(&self.action), id);
Ok(())
}
}
impl MutableHookDataAccessor for FlockUpdateHook {
fn access_mut(&self, fle: &mut FileLockEntry) -> HookResult<()> {
debug!("[FLOCK HOOK][{}] {:?}", action_to_str(&self.action), fle.get_location());
fle.lock()
.map_err(|e| HookError::new(HookErrorKind::HookExecutionError, Some(Box::new(e))))
.map(|_| ())
}
}
impl NonMutableHookDataAccessor for FlockUpdateHook {
fn access(&self, fle: &FileLockEntry) -> HookResult<()> {
debug!("[FLOCK HOOK][{}] {:?}", action_to_str(&self.action), fle.get_location());
fle.unlock()
.map_err(|e| HookError::new(HookErrorKind::HookExecutionError, Some(Box::new(e))))
.map(|_| ())
}
}

View file

@ -1,52 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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
//
#![deny(
dead_code,
non_camel_case_types,
non_snake_case,
path_statements,
trivial_numeric_casts,
unstable_features,
unused_allocation,
unused_import_braces,
unused_imports,
unused_must_use,
unused_mut,
unused_qualifications,
while_true,
)]
#[macro_use] extern crate log;
extern crate toml;
extern crate fs2;
extern crate git2;
extern crate libimagstore;
extern crate libimagentrylink;
extern crate libimaginteraction;
#[macro_use] extern crate libimagerror;
extern crate libimagutil;
pub mod debug;
pub mod denylinkeddelete;
pub mod flock;
pub mod linkverify;
pub mod vcs;

View file

@ -1,93 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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 toml::Value;
use libimagstore::hook::Hook;
use libimagstore::hook::accessor::HookDataAccessor as HDA;
use libimagstore::hook::accessor::HookDataAccessorProvider;
use libimagstore::hook::accessor::NonMutableHookDataAccessor;
use libimagstore::hook::result::HookResult;
use libimagstore::store::FileLockEntry;
use libimagentrylink::internal::InternalLinker;
use libimagerror::trace::trace_error;
#[derive(Debug, Clone)]
pub struct LinkedEntriesExistHook {
store_location: PathBuf,
}
impl LinkedEntriesExistHook {
pub fn new(store_location: PathBuf) -> LinkedEntriesExistHook {
LinkedEntriesExistHook {
store_location: store_location,
}
}
}
impl Hook for LinkedEntriesExistHook {
fn name(&self) -> &'static str {
"stdhook_linked_entries_exist"
}
fn set_config(&mut self, _: &Value) {
() // We are not configurable here.
}
}
impl HookDataAccessorProvider for LinkedEntriesExistHook {
fn accessor(&self) -> HDA {
HDA::NonMutableAccess(self)
}
}
impl NonMutableHookDataAccessor for LinkedEntriesExistHook {
fn access(&self, fle: &FileLockEntry) -> HookResult<()> {
use libimagstore::hook::error::HookErrorKind;
use libimagstore::hook::error::MapErrInto;
debug!("[LINKVERIFY HOOK] {:?}", fle.get_location());
match fle.get_internal_links() {
Ok(links) => {
for link in links {
if !try!(link.exists().map_err_into(HookErrorKind::HookExecutionError)) {
warn!("File link does not exist: {:?} -> {:?}", fle.get_location(), link);
}
}
Ok(())
},
Err(e) => {
warn!("Couldn't execute Link-Verify hook");
trace_error(&e);
Err(e).map_err_into(HookErrorKind::HookExecutionError)
}
}
}
}

View file

@ -1,69 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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::fmt::{Display, Formatter, Error};
/// Utility type to specify which kind of store action is running
#[derive(Clone, Debug)]
pub enum StoreAction {
Create,
Retrieve,
Update,
Delete,
StoreUnload,
}
impl StoreAction {
pub fn uppercase(&self) -> &str {
match *self {
StoreAction::Create => "CREATE",
StoreAction::Retrieve => "RETRIEVE",
StoreAction::Update => "UPDATE",
StoreAction::Delete => "DELETE",
StoreAction::StoreUnload => "STORE UNLOAD",
}
}
pub fn as_commit_message(&self) -> &str {
match *self {
StoreAction::Create => "Create",
StoreAction::Retrieve => "Retrieve",
StoreAction::Update => "Update",
StoreAction::Delete => "Delete",
StoreAction::StoreUnload => "Store Unload",
}
}
}
impl Display for StoreAction {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
write!(fmt, "StoreAction: {}",
match *self {
StoreAction::Create => "create",
StoreAction::Retrieve => "retrieve",
StoreAction::Update => "update",
StoreAction::Delete => "delete",
StoreAction::StoreUnload => "store unload",
})
}
}

View file

@ -1,241 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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 toml::Value;
use libimagerror::into::IntoError;
use libimagerror::trace::trace_error;
use libimagstore::storeid::StoreId;
use libimagstore::toml_ext::TomlValueExt;
use vcs::git::error::GitHookErrorKind as GHEK;
use vcs::git::error::MapErrInto;
use vcs::git::result::Result;
use vcs::git::action::StoreAction;
use git2::Repository;
/// Check the configuration whether we should commit interactively
pub fn commit_interactive(config: &Value, action: &StoreAction) -> bool {
match config.read("commit.interactive") {
Ok(Some(Value::Boolean(b))) => b,
Ok(Some(_)) => {
warn!("Configuration error, 'store.hooks.stdhook_git_{}.commit.interactive' must be a Boolean.",
action);
warn!("Defaulting to commit.interactive = false");
false
}
Ok(None) => {
warn!("Unavailable configuration for");
warn!("\t'store.hooks.stdhook_git_{}.commit.interactive'", action);
warn!("Defaulting to false");
false
},
Err(e) => {
error!("Error parsing TOML:");
trace_error(&e);
false
},
}
}
/// Check the configuration whether we should commit with the editor
fn commit_with_editor(config: &Value, action: &StoreAction) -> bool {
match config.read("commit.interactive_editor") {
Ok(Some(Value::Boolean(b))) => b,
Ok(Some(_)) => {
warn!("Configuration error, 'store.hooks.stdhook_git_{}.commit.interactive_editor' must be a Boolean.",
action);
warn!("Defaulting to commit.interactive_editor = false");
false
}
Ok(None) => {
warn!("Unavailable configuration for");
warn!("\t'store.hooks.stdhook_git_{}.commit.interactive_editor'", action);
warn!("Defaulting to false");
false
},
Err(e) => {
error!("Error parsing TOML:");
trace_error(&e);
false
},
}
}
/// Get the commit default message
fn commit_default_msg<'a>(config: &'a Value, action: &'a StoreAction) -> Result<String> {
config.read("commit.message")
.map(|m| match m {
Some(Value::String(b)) => String::from(b),
Some(_) => {
warn!("Configuration error, 'store.hooks.stdhook_git_{}.commit.message' must be a String.",
action);
warn!("Defaulting to commit.message = '{}'", action.as_commit_message());
String::from(action.as_commit_message())
},
None => {
warn!("Unavailable configuration for");
warn!("\t'store.hooks.stdhook_git_{}.commit.message'", action);
warn!("Defaulting to commit.message = '{}'", action.as_commit_message());
String::from(action.as_commit_message())
},
})
.map_err_into(GHEK::ConfigError)
}
/// Get the commit template
///
/// TODO: Implement good template string
fn commit_template(action: &StoreAction, id: &StoreId) -> String {
format!(r#"
# Please commit your changes and remove these lines.
#
# You're about to commit changes via the {action} Hook
#
# Altered file: {id}
#
"#,
action = action,
id = id.local().display())
}
/// Generate a commit message
///
/// Uses the functions `commit_interactive()` and `commit_with_editor()`
/// or reads one from the commandline or uses the `commit_default_msg()` string to create a commit
/// message.
pub fn commit_message(repo: &Repository, config: &Value, action: StoreAction, id: &StoreId) -> Result<String> {
use libimaginteraction::ask::ask_string;
use libimagutil::edit::edit_in_tmpfile_with_command;
use std::process::Command;
if commit_interactive(config, &action) {
if commit_with_editor(config, &action) {
repo.config()
.map_err_into(GHEK::GitConfigFetchError)
.and_then(|c| c.get_string("core.editor").map_err_into(GHEK::GitConfigEditorFetchError))
.map_err_into(GHEK::ConfigError)
.map(Command::new)
.and_then(|cmd| {
let mut s = commit_template(&action, id);
edit_in_tmpfile_with_command(cmd, &mut s).map(|_| s)
.map_err_into(GHEK::EditorError)
})
} else {
Ok(ask_string("Commit Message", None, false, false, None, "> "))
}
} else {
commit_default_msg(config, &action)
}
}
/// Check whether the hook should abort if the repository cannot be initialized
pub fn abort_on_repo_init_err(cfg: &Value) -> bool {
get_bool_cfg(Some(cfg), "abort_on_repo_init_failure", true, true)
}
/// Get the branch which must be checked out before running the hook (if any).
///
/// If there is no configuration for this, this is `Ok(None)`, otherwise we try to find the
/// configuration `String`.
pub fn ensure_branch(cfg: Option<&Value>) -> Result<Option<String>> {
match cfg {
Some(cfg) => {
cfg.read("ensure_branch")
.map_err_into(GHEK::ConfigError)
.and_then(|toml| match toml {
Some(Value::String(ref s)) => Ok(Some(s.clone())),
Some(_) => {
warn!("Configuration error, 'ensure_branch' must be a String.");
Err(GHEK::ConfigTypeError.into_error())
.map_err_into(GHEK::ConfigTypeError)
},
None => {
debug!("No key `ensure_branch'");
Ok(None)
},
})
},
None => Ok(None),
}
}
/// Check whether we should check out a branch before committing.
pub fn do_checkout_ensure_branch(cfg: Option<&Value>) -> bool {
get_bool_cfg(cfg, "try_checkout_ensure_branch", true, true)
}
/// Helper to get a boolean value from the configuration.
fn get_bool_cfg(cfg: Option<&Value>, name: &str, on_fail: bool, on_unavail: bool) -> bool {
cfg.map(|cfg| {
match cfg.read(name) {
Ok(Some(Value::Boolean(b))) => b,
Ok(Some(_)) => {
warn!("Configuration error, '{}' must be a Boolean (true|false).", name);
warn!("Assuming '{}' now.", on_fail);
on_fail
},
Ok(None) => {
warn!("No key '{}' - Assuming '{}'", name, on_unavail);
on_unavail
},
Err(e) => {
error!("Error parsing TOML:");
trace_error(&e);
false
},
}
})
.unwrap_or_else(|| {
warn!("No configuration to fetch {} from, assuming {}", name, on_unavail);
on_unavail
})
}
/// Check whether the hook is enabled or not. If the config is not there, the hook is _enabled_ by
/// default.
pub fn is_enabled(cfg: &Value) -> bool {
get_bool_cfg(Some(cfg), "enabled", true, true)
}
/// Check whether committing is enabled for a hook.
pub fn committing_is_enabled(cfg: &Value) -> Result<bool> {
cfg.read("commit.enabled")
.map_err_into(GHEK::ConfigError)
.and_then(|toml| match toml {
Some(Value::Boolean(b)) => Ok(b),
Some(_) => {
warn!("Config setting whether committing is enabled or not has wrong type.");
warn!("Expected Boolean");
Err(GHEK::ConfigTypeError.into_error())
},
None => {
warn!("No config setting whether committing is enabled or not.");
Err(GHEK::NoConfigError.into_error())
},
})
}
pub fn add_wt_changes_before_committing(cfg: &Value) -> bool {
get_bool_cfg(Some(cfg), "commit.add_wt_changes", true, true)
}

View file

@ -1,242 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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::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;
pub struct DeleteHook {
storepath: PathBuf,
runtime: GRuntime,
position: HookPosition,
}
impl DeleteHook {
pub fn new(storepath: PathBuf, p: HookPosition) -> DeleteHook {
DeleteHook {
runtime: GRuntime::new(&storepath),
storepath: storepath,
position: p,
}
}
}
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) {
if let Err(e) = self.runtime.set_config(config) {
trace_error(&e);
}
}
}
impl HookDataAccessorProvider for DeleteHook {
fn accessor(&self) -> HookDataAccessor {
HookDataAccessor::StoreIdAccess(self)
}
}
impl StoreIdAccessor for DeleteHook {
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;
use git2::{ADD_DEFAULT, STATUS_WT_DELETED, IndexMatchedPath};
debug!("[GIT DELETE HOOK]: {:?}", id);
let action = StoreAction::Delete;
let cfg = try!(self.runtime.config_value_or_err(&action));
if !is_enabled(cfg) {
return Ok(())
}
if !self.runtime.has_repository() {
debug!("[GIT DELETE 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 DELETE HOOK]: Config says we should abort if we have no repository");
debug!("[GIT DELETE HOOK]: Returing Err(_)");
return Err(GHEK::RepositoryInitError.into_error())
.map_err_into(GHEK::RepositoryError)
.map_into_hook_error()
} else {
debug!("[GIT DELETE HOOK]: Config says it is okay to not have a repository");
debug!("[GIT DELETE 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 DELETE 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 DELETE HOOK]: Fetched 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_dbg_str("[GIT DELETE HOOK]: Fetched file status")
.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_dbg_str("[GIT DELETE HOOK]: Fetched 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_dbg_str("[GIT DELETE 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 DELETE HOOK]: Found commit HEAD")
.map_into_hook_error()
);
parents.push(commit);
}
// for converting from Vec<Commit> to Vec<&Commit>
let parents = parents.iter().collect::<Vec<_>>();
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 DELETE 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 DELETE 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 DELETE 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 DELETE HOOK]: Wrote index")
.map_into_hook_error()
.map(|_| ())
}
}

View file

@ -1,97 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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 git2::Error as Git2Error;
use libimagstore::hook::error::HookError as HE;
use libimagstore::hook::error::HookErrorKind as HEK;
use libimagstore::hook::result::HookResult;
generate_error_module!(
generate_error_types!(GitHookError, GitHookErrorKind,
ConfigError => "Configuration Error",
NoConfigError => "No Configuration",
ConfigTypeError => "Configuration value type wrong",
RepositoryError => "Error while interacting with git repository",
RepositoryInitError => "Error while loading the git repository",
RepositoryBackendError => "Error in the git library",
RepositoryBranchError => "Error while interacting with git branch(es)",
RepositoryBranchNameFetchingError => "Error while fetching branch name",
RepositoryWrongBranchError => "Error because repository is on wrong branch",
RepositoryIndexFetchingError => "Error while fetching Repository Index",
RepositoryIndexWritingError => "Error while writing Repository Index",
RepositoryPathAddingError => "Error while adding Path to Index",
RepositoryCommittingError => "Error while committing",
RepositoryParentFetchingError => "Error while fetching parent of commit",
RepositoryStatusFetchError => "Error while fetching repository status",
HeadFetchError => "Error while getting HEAD",
NotOnBranch => "No Branch is checked out",
MkRepo => "Repository creation error",
MkSignature => "Error while building Signature object",
RepositoryFileStatusError => "Error while getting file status",
GitConfigFetchError => "Error fetching git config",
GitConfigEditorFetchError => "Error fetching 'editor' from git config",
EditorError => "Error while calling editor"
);
);
impl GitHookError {
pub fn inside_of<T>(self, h: HEK) -> HookResult<T> {
Err(HE::new(h, Some(Box::new(self))))
}
}
impl From<GitHookError> for HE {
fn from(he: GitHookError) -> HE {
HE::new(HEK::HookExecutionError, Some(Box::new(he)))
}
}
impl From<Git2Error> for GitHookError {
fn from(ge: Git2Error) -> GitHookError {
GitHookError::new(GitHookErrorKind::RepositoryBackendError, Some(Box::new(ge)))
}
}
pub trait MapIntoHookError<T> {
fn map_into_hook_error(self) -> Result<T, HE>;
}
impl<T> MapIntoHookError<T> for Result<T, GitHookError> {
fn map_into_hook_error(self) -> Result<T, HE> {
self.map_err(|e| HE::new(HEK::HookExecutionError, Some(Box::new(e))))
}
}
pub use self::error::GitHookError;
pub use self::error::GitHookErrorKind;
pub use self::error::MapErrInto;

View file

@ -1,29 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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
//
mod action;
mod config;
pub mod delete;
mod error;
mod result;
mod runtime;
pub mod store_unload;
pub mod update;
pub mod util;

View file

@ -1,24 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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::result::Result as RResult;
use vcs::git::error::GitHookError;
pub type Result<T> = RResult<T, GitHookError>;

View file

@ -1,178 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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 git2::Repository;
use toml::Value;
use libimagerror::into::IntoError;
use libimagerror::trace::MapErrTrace;
use libimagstore::hook::error::CustomData;
use libimagstore::hook::error::HookErrorKind as HEK;
use libimagstore::hook::result::HookResult;
use libimagutil::debug_result::*;
use vcs::git::action::StoreAction;
use vcs::git::result::Result;
use vcs::git::error::{MapErrInto, GitHookErrorKind as GHEK};
/// Runtime object for git hook implementations.
///
/// Contains some utility functionality to hold the repository and the configuration for the hooks.
pub struct Runtime {
repository: Option<Repository>,
config: Option<Value>,
}
impl Runtime {
/// Build a `Runtime` object, pass the store path to build the `Repository` instance the
/// `Runtime` has to contain.
///
/// If the building of the `Repository` fails, this function `trace_error()`s the error and
/// returns a `Runtime` object that does _not_ contain a `Repository`.
pub fn new(storepath: &PathBuf) -> Runtime {
Runtime {
repository: Repository::open(storepath).map_err_trace().ok(),
config: None,
}
}
/// Set the configuration for the `Runtime`. Always returns `Ok(())`.
pub fn set_config(&mut self, cfg: &Value) -> Result<()> {
self.config = Some(cfg.clone());
Ok(())
}
/// Check whether the `Runtime` has a `Repository`
pub fn has_repository(&self) -> bool {
self.repository.is_some()
}
/// Check whether the `Runtime` has a configuration
pub fn has_config(&self) -> bool {
self.config.is_some()
}
/// Get the the config value by reference or get an `Err()` which can be returned to the callee
/// of the Hook.
///
/// The `action` Argument is required in case of `Err()` so the error message can be build
/// correctly.
pub fn config_value_or_err(&self, action: &StoreAction) -> HookResult<&Value> {
self.config
.as_ref()
.ok_or(GHEK::NoConfigError.into_error())
.map_err_into(GHEK::ConfigError)
.map_err(Box::new)
.map_err(|e| HEK::HookExecutionError.into_error_with_cause(e))
.map_err(|e| e.with_custom_data(CustomData::default().aborting(false)))
.map_dbg_err(|_| {
format!("[GIT {} HOOK]: Couldn't get Value object from config", action.uppercase())
})
}
/// Get the `Repository` object from the `Runtime` or an `Err()` that can be returned to the
/// callee of the Hook.
///
/// The `action` Argument is required in case of `Err()` so the error message can be build
/// correctly.
pub fn repository(&self, action: &StoreAction) -> HookResult<&Repository> {
use vcs::git::error::MapIntoHookError;
debug!("[GIT {} HOOK]: Getting repository", action.uppercase());
self.repository
.as_ref()
.ok_or(GHEK::MkRepo.into_error())
.map_err_into(GHEK::RepositoryError)
.map_into_hook_error()
.map_dbg_err(|_| format!("[GIT {} HOOK]: Couldn't fetch Repository", action.uppercase()))
.map_dbg(|_| format!("[GIT {} HOOK]: Repository object fetched", action.uppercase()))
}
/// Ensure that the branch that is put in the configuration file is checked out, if any.
pub fn ensure_cfg_branch_is_checked_out(&self, action: &StoreAction) -> HookResult<()> {
use vcs::git::config::ensure_branch;
use vcs::git::config::do_checkout_ensure_branch;
debug!("[GIT {} HOOK]: Ensuring branch checkout", action.uppercase());
let head = try!(self
.repository(action)
.and_then(|r| {
debug!("[GIT {} HOOK]: Repository fetched, getting head", action.uppercase());
r.head()
.map_dbg_err_str("Couldn't fetch HEAD")
.map_dbg_err(|e| format!("\tbecause = {:?}", e))
.map_err_into(GHEK::HeadFetchError)
.map_err(|e| e.into())
}));
debug!("[GIT {} HOOK]: HEAD fetched", action.uppercase());
// TODO: Fail if not on branch? hmmh... I'm not sure
if !head.is_branch() {
debug!("[GIT {} HOOK]: HEAD is not a branch", action.uppercase());
return Err(GHEK::NotOnBranch.into_error().into());
}
debug!("[GIT {} HOOK]: HEAD is a branch", action.uppercase());
// Check out appropriate branch ... or fail
match ensure_branch(self.config.as_ref()) {
Ok(Some(s)) => {
debug!("[GIT {} HOOK]: We have to ensure branch: {}", action.uppercase(), s);
match head.name().map(|name| {
debug!("[GIT {} HOOK]: {} == {}", action.uppercase(), name, s);
name == s
}) {
Some(b) => {
if b {
debug!("[GIT {} HOOK]: Branch already checked out.", action.uppercase());
Ok(())
} else {
debug!("[GIT {} HOOK]: Branch not checked out.", action.uppercase());
if !do_checkout_ensure_branch(self.config.as_ref()) {
Err(GHEK::RepositoryWrongBranchError.into_error())
.map_err_into(GHEK::RepositoryError)
} else {
// Else try to check out the branch...
unimplemented!()
}
}
},
None => Err(GHEK::RepositoryBranchNameFetchingError.into_error())
.map_err_into(GHEK::RepositoryBranchError)
.map_err_into(GHEK::RepositoryError),
}
},
Ok(None) => {
debug!("[GIT {} HOOK]: No branch to checkout", action.uppercase());
Ok(())
},
Err(e) => Err(e).map_err_into(GHEK::RepositoryError),
}
.map_err(Box::new)
.map_err(|e| HEK::HookExecutionError.into_error_with_cause(e))
.map_dbg(|_| format!("[GIT {} HOOK]: Branch checked out", action.uppercase()))
}
}

View file

@ -1,264 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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;
use vcs::git::config::add_wt_changes_before_committing;
use git2::{ADD_DEFAULT,
StatusOptions,
Status,
StatusShow as STShow,
STATUS_INDEX_NEW as I_NEW,
STATUS_INDEX_DELETED as I_DEL,
STATUS_INDEX_RENAMED as I_REN,
STATUS_INDEX_MODIFIED as I_MOD,
STATUS_WT_NEW as WT_NEW,
STATUS_WT_DELETED as WT_DEL,
STATUS_WT_RENAMED as WT_REN,
STATUS_WT_MODIFIED as WT_MOD};
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 check_dirty = |show: STShow, new: Status, modif: Status, del: Status, ren: Status| {
let mut status_options = StatusOptions::new();
status_options.show(show);
status_options.include_untracked(true);
repo.statuses(Some(&mut status_options))
.map(|statuses| {
statuses.iter()
.map(|s| s.status())
.map(|s| {
debug!("STATUS_WT_NEW = {}", s == new);
debug!("STATUS_WT_MODIFIED = {}", s == modif);
debug!("STATUS_WT_DELETED = {}", s == del);
debug!("STATUS_WT_RENAMED = {}", s == ren);
s
})
.any(|s| s == new || s == modif || s == del || s == ren)
})
.map_err_into(GHEK::RepositoryStatusFetchError)
.map_err_into(GHEK::RepositoryError)
.map_into_hook_error()
};
if try!(check_dirty(STShow::Workdir, WT_NEW, WT_MOD, WT_DEL, WT_REN)) {
if add_wt_changes_before_committing(cfg) {
debug!("Adding WT changes before committing.");
try!(index.add_all(&["*"], ADD_DEFAULT, None)
.map_err_into(GHEK::RepositoryPathAddingError)
.map_err_into(GHEK::RepositoryError)
.map_into_hook_error());
} else {
warn!("WT dirty, but adding files before committing on Drop disabled.");
warn!("Continuing without adding changes to the index.");
}
} else {
debug!("WT not dirty.");
}
if try!(check_dirty(STShow::Index, I_NEW, I_MOD, I_DEL, I_REN)) {
debug!("INDEX DIRTY!");
} else {
debug!("INDEX CLEAN... not continuing!");
return Ok(());
}
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<Commit> to Vec<&Commit>
let parents = parents.iter().collect::<Vec<_>>();
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(|_| ())
}
}

View file

@ -1,263 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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::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::hook::Hook;
use libimagstore::hook::accessor::StoreIdAccessor;
use libimagstore::hook::accessor::{HookDataAccessor, HookDataAccessorProvider};
use libimagstore::hook::position::HookPosition;
use libimagstore::hook::result::HookResult;
use libimagstore::storeid::StoreId;
use libimagutil::debug_result::*;
use vcs::git::error::GitHookErrorKind as GHEK;
use vcs::git::error::MapErrInto;
use vcs::git::runtime::Runtime as GRuntime;
/// The `UpdateHook` type
///
/// Represents a hook which is executed whenever a entry in the store is updated (written to disk).
///
/// # Time of execution
///
/// This hook is executed _after_ the store operation succeeded, so _after_ the file is written to
/// disk.
pub struct UpdateHook {
storepath: PathBuf,
runtime: GRuntime,
position: HookPosition,
}
impl UpdateHook {
pub fn new(storepath: PathBuf, p: HookPosition) -> UpdateHook {
UpdateHook {
runtime: GRuntime::new(&storepath),
storepath: storepath,
position: p,
}
}
}
impl Debug for UpdateHook {
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
write!(fmt, "UpdateHook(storepath={:?}, repository={}, pos={:?}, cfg={:?})",
self.storepath,
(if self.runtime.has_repository() { "Some(_)" } else { "None" }),
self.position,
self.runtime.has_config())
}
}
impl Hook for UpdateHook {
fn name(&self) -> &'static str {
"stdhook_git_update"
}
/// 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 UpdateHook {
fn accessor(&self) -> HookDataAccessor {
HookDataAccessor::StoreIdAccess(self)
}
}
impl StoreIdAccessor for UpdateHook {
/// The implementation of the UpdateHook
///
/// # Scope
///
/// This hook takes the git index and commits it either interactively or with a default message,
/// if there is no configuration for an interactive commit.
///
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;
use git2::{ADD_DEFAULT, STATUS_WT_NEW, STATUS_WT_MODIFIED, IndexMatchedPath};
debug!("[GIT UPDATE HOOK]: {:?}", id);
let action = StoreAction::Update;
let cfg = try!(self.runtime.config_value_or_err(&action));
if !is_enabled(cfg) {
return Ok(());
}
if !self.runtime.has_repository() {
debug!("[GIT UPDATE 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 UPDATE HOOK]: Config says we should abort if we have no repository");
debug!("[GIT UPDATE HOOK]: Returing Err(_)");
return Err(GHEK::RepositoryInitError.into_error())
.map_err_into(GHEK::RepositoryError)
.map_into_hook_error()
} else {
debug!("[GIT UPDATE HOOK]: Config says it is okay to not have a repository");
debug!("[GIT UPDATE HOOK]: Returing Ok(())");
return Ok(())
}
}
let _ = try!(self.runtime.ensure_cfg_branch_is_checked_out(&action));
let repo = try!(self.runtime.repository(&action));
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_dbg_str("[GIT UPDATE HOOK]: Fetched file status")
.map_err_into(GHEK::RepositoryFileStatusError)
.map_into_hook_error()
);
debug!("File status: STATUS_WT_NEW = {}", file_status.contains(STATUS_WT_NEW));
debug!("File status: STATUS_WT_MODIFIED = {}", file_status.contains(STATUS_WT_MODIFIED));
if !file_status.contains(STATUS_WT_NEW) && !file_status.contains(STATUS_WT_MODIFIED) {
// File seems to be unmodified and not new. This means that the file is already
// committed and we can return here.
return Ok(())
}
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 UPDATE 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 UPDATE HOOK]: Fetched HEAD")
.map_into_hook_error()
);
let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
if file_status.contains(STATUS_WT_NEW) || file_status.contains(STATUS_WT_MODIFIED) {
debug!("[GIT CREATE HOOK]: File is modified/new: {}", path.display());
0
} else {
debug!("[GIT CREATE 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_dbg(|_| format!("[GIT UPDATE HOOK]: Added id ({:?}) to index", id))
.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 UPDATE HOOK]: Wrote 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 UPDATE HOOK]: Fetched commit parents of HEAD")
.map_into_hook_error()
);
parents.push(commit);
}
// for converting from Vec<Commit> to Vec<&Commit>
let parents = parents.iter().collect::<Vec<_>>();
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 UPDATE HOOK]: Found Tree for parents")
.map_into_hook_error()
);
let message = try!(commit_message(&repo, cfg, StoreAction::Update, &id)
.map_dbg_err_str("Failed to get commit message")
.map_dbg_str("[GIT UPDATE HOOK]: Fetched 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(|_| ())
}
}

View file

@ -1,42 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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
//
//! Utility functionality for integrating git hooks in the store
//!
//! Contains primitives to create a repository within the store path
use git2::{Repository, Index};
use vcs::git::error::GitHookErrorKind as GHEK;
use vcs::git::error::MapErrInto;
use vcs::git::action::StoreAction;
use vcs::git::error::MapIntoHookError;
use libimagutil::debug_result::*;
use libimagstore::hook::error::HookError;
pub fn fetch_index(repo: &Repository, action: &StoreAction) -> Result<Index, HookError> {
debug!("[GIT {} HOOK]: Getting Index", action.uppercase());
repo.index()
.map_dbg_err(|_| format!("[GIT {} HOOK]: Couldn't fetch Index", action.uppercase()))
.map_dbg(|_| format!("[GIT {} HOOK]: Index object fetched", action.uppercase()))
.map_err_into(GHEK::RepositoryIndexFetchingError)
.map_into_hook_error()
}

View file

@ -1,20 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> 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
//
pub mod git;