Merge pull request #951 from matthiasbeyer/remove-hooks
libimagstore: Remove hook support
This commit is contained in:
commit
bb9ff5bfd8
41 changed files with 340 additions and 4122 deletions
|
@ -60,9 +60,6 @@ path = "../libimagrt"
|
|||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
||||
[dependencies.libimagstorestdhook]
|
||||
path = "../libimagstorestdhook"
|
||||
|
||||
[dependencies.libimagtimeui]
|
||||
path = "../libimagtimeui"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -31,7 +31,6 @@ members = [
|
|||
"libimagref",
|
||||
"libimagrt",
|
||||
"libimagstore",
|
||||
"libimagstorestdhook",
|
||||
"libimagtimeui",
|
||||
"libimagtodo",
|
||||
"libimagutil",
|
||||
|
|
|
@ -12,5 +12,4 @@ to live in a imag binary.
|
|||
### Long-term TODO
|
||||
|
||||
- [ ] Merge with `libimagstore`
|
||||
- [ ] Merge with `libimagstorestdhook`
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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`
|
||||
|
||||
|
|
145
imagrc.toml
145
imagrc.toml
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -26,9 +26,6 @@ ansi_term = "0.9"
|
|||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
||||
[dependencies.libimagstorestdhook]
|
||||
path = "../libimagstorestdhook"
|
||||
|
||||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ extern crate clap;
|
|||
extern crate toml;
|
||||
|
||||
extern crate libimagstore;
|
||||
extern crate libimagstorestdhook;
|
||||
extern crate libimagutil;
|
||||
#[macro_use] extern crate libimagerror;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -66,3 +66,8 @@ verify = []
|
|||
#
|
||||
early-panic=[]
|
||||
|
||||
# File system locking
|
||||
#
|
||||
# Enable this feature to enable file-system locking in the store.
|
||||
fs-locking = []
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
|
@ -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>;
|
|
@ -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
|
@ -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"
|
||||
|
|
@ -1 +0,0 @@
|
|||
../doc/src/05100-lib-store-std-hook.md
|
|
@ -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(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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(|_| ())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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(|_| ())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>;
|
|
@ -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()))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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(|_| ())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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(|_| ())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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;
|
Loading…
Reference in a new issue