diff --git a/imagrc.toml b/imagrc.toml index 69142ce2..ac323962 100644 --- a/imagrc.toml +++ b/imagrc.toml @@ -94,3 +94,17 @@ ensure_branch = "master" # Try to checkout the ensure_branch if it isn't checked out try_checkout_ensure_branch = true +# Commit configuration +[store.hooks.stdhook_git_update.commit] + +# Whether to do the commit interactively +interactive = false + +# Set to true to use the $EDITOR for the commit, to false to do on commandline +# When committing without editor, only a single line is allowed as commit +# message +interactive_editor = false + +# Commit message if the commit is not interactive +message = "Update" + diff --git a/libimagentryedit/Cargo.toml b/libimagentryedit/Cargo.toml index 0c79700e..076519fe 100644 --- a/libimagentryedit/Cargo.toml +++ b/libimagentryedit/Cargo.toml @@ -4,7 +4,6 @@ version = "0.2.0" authors = ["Matthias Beyer "] [dependencies] -tempfile = "2.1.1" [dependencies.libimagerror] path = "../libimagerror" @@ -15,3 +14,6 @@ path = "../libimagrt" [dependencies.libimagstore] path = "../libimagstore" +[dependencies.libimagutil] +path = "../libimagutil" + diff --git a/libimagentryedit/src/edit.rs b/libimagentryedit/src/edit.rs index cdf976a4..08e2bfb4 100644 --- a/libimagentryedit/src/edit.rs +++ b/libimagentryedit/src/edit.rs @@ -39,39 +39,20 @@ impl<'a> Edit for FileLockEntry<'a> { } pub fn edit_in_tmpfile(rt: &Runtime, s: &mut String) -> Result<()> { - use tempfile::NamedTempFile; - use std::io::Seek; - use std::io::Read; - use std::io::SeekFrom; - use std::io::Write; + use libimagutil::edit::edit_in_tmpfile_with_command; - let file = try!(NamedTempFile::new().map_err_into(EditErrorKind::IOError)); - let file_path = file.path(); - let mut file = try!(file.reopen().map_err_into(EditErrorKind::IOError)); - - try!(file.write_all(&s.clone().into_bytes()[..]).map_err_into(EditErrorKind::IOError)); - try!(file.sync_data().map_err_into(EditErrorKind::IOError)); - - if let Some(mut editor) = rt.editor() { - let exit_status = editor.arg(file_path).status(); - - match exit_status.map(|s| s.success()).map_err(Box::new) { - Ok(true) => { - file.sync_data() - .and_then(|_| file.seek(SeekFrom::Start(0))) - .and_then(|_| { - let mut new_s = String::new(); - let res = file.read_to_string(&mut new_s); - *s = new_s; - res - }) - .map(|_| ()) - .map_err_into(EditErrorKind::IOError) - }, - Ok(false) => Err(EditErrorKind::ProcessExitFailure.into()), - Err(e) => Err(EditErrorKind::IOError.into_error_with_cause(e)), - } - } else { - Err(EditErrorKind::InstantiateError.into()) - } + rt.editor() + .ok_or(EditErrorKind::NoEditor.into_error()) + .and_then(|editor| { + edit_in_tmpfile_with_command(editor, s) + .map_err_into(EditErrorKind::IOError) + .and_then(|worked| { + if !worked { + Err(EditErrorKind::ProcessExitFailure.into()) + } else { + Ok(()) + } + }) + }) } + diff --git a/libimagentryedit/src/error.rs b/libimagentryedit/src/error.rs index 0cd675e0..776a51fb 100644 --- a/libimagentryedit/src/error.rs +++ b/libimagentryedit/src/error.rs @@ -1,6 +1,7 @@ generate_error_module!( generate_error_types!(EditError, EditErrorKind, IOError => "IO Error", + NoEditor => "No editor set", ProcessExitFailure => "Process did not exit properly", InstantiateError => "Instantation error" ); diff --git a/libimagentryedit/src/lib.rs b/libimagentryedit/src/lib.rs index 62cff1e4..d0a26038 100644 --- a/libimagentryedit/src/lib.rs +++ b/libimagentryedit/src/lib.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate libimagerror; extern crate libimagstore; extern crate libimagrt; -extern crate tempfile; +extern crate libimagutil; pub mod edit; pub mod error; diff --git a/libimagstore/src/store.rs b/libimagstore/src/store.rs index 6d58b531..5922b35f 100644 --- a/libimagstore/src/store.rs +++ b/libimagstore/src/store.rs @@ -1440,7 +1440,7 @@ impl Entry { } pub fn to_str(&self) -> String { - format!("---{header}---\n{content}", + format!("---\n{header}---\n{content}", header = ::toml::encode_str(&self.header.header), content = self.content) } diff --git a/libimagstorestdhook/Cargo.toml b/libimagstorestdhook/Cargo.toml index cd1eeeb4..912079b2 100644 --- a/libimagstorestdhook/Cargo.toml +++ b/libimagstorestdhook/Cargo.toml @@ -15,6 +15,9 @@ path = "../libimagstore" [dependencies.libimagentrylink] path = "../libimagentrylink" +[dependencies.libimaginteraction] +path = "../libimaginteraction" + [dependencies.libimagerror] path = "../libimagerror" diff --git a/libimagstorestdhook/src/lib.rs b/libimagstorestdhook/src/lib.rs index a99ff515..76b15b7e 100644 --- a/libimagstorestdhook/src/lib.rs +++ b/libimagstorestdhook/src/lib.rs @@ -21,6 +21,7 @@ extern crate git2; extern crate libimagstore; extern crate libimagentrylink; +extern crate libimaginteraction; #[macro_use] extern crate libimagerror; extern crate libimagutil; diff --git a/libimagstorestdhook/src/vcs/git/config.rs b/libimagstorestdhook/src/vcs/git/config.rs index a7e262d6..95e09a42 100644 --- a/libimagstorestdhook/src/vcs/git/config.rs +++ b/libimagstorestdhook/src/vcs/git/config.rs @@ -1,6 +1,7 @@ use toml::Value; use libimagerror::into::IntoError; +use libimagutil::edit::edit_in_tmpfile_with_command; use vcs::git::error::GitHookErrorKind as GHEK; use vcs::git::error::MapErrInto; @@ -8,16 +9,85 @@ use vcs::git::result::Result; use vcs::git::action::StoreAction; +use git2::Repository; + pub fn commit_interactive(config: &Value) -> bool { - warn!("Interactive committing not yet supported, using dummy commit message"); - false + match config.lookup("commit.interactive") { + Some(&Value::Boolean(b)) => b, + Some(_) => { + warn!("Configuration error, 'store.hooks.stdhook_git_update.commit.interactive' must be a Boolean."); + warn!("Defaulting to commit.interactive = false"); + false + } + None => { + warn!("Unavailable configuration for"); + warn!("\t'store.hooks.stdhook_git_update.commit.interactive'"); + warn!("Defaulting to false"); + false + } + } } -pub fn commit_message(config: &Value, action: StoreAction) -> Result { +fn commit_with_editor(config: &Value) -> bool { + match config.lookup("commit.interactive_editor") { + Some(&Value::Boolean(b)) => b, + Some(_) => { + warn!("Configuration error, 'store.hooks.stdhook_git_update.commit.interactive_editor' must be a Boolean."); + warn!("Defaulting to commit.interactive_editor = false"); + false + } + None => { + warn!("Unavailable configuration for"); + warn!("\t'store.hooks.stdhook_git_update.commit.interactive_editor'"); + warn!("Defaulting to false"); + false + } + } +} + +fn commit_default_msg<'a>(config: &'a Value) -> &'a str { + match config.lookup("commit.message") { + Some(&Value::String(ref b)) => b, + Some(_) => { + warn!("Configuration error, 'store.hooks.stdhook_git_update.commit.message' must be a String."); + warn!("Defaulting to commit.message = 'Update'"); + "Update" + } + None => { + warn!("Unavailable configuration for"); + warn!("\t'store.hooks.stdhook_git_update.commit.message'"); + warn!("Defaulting to commit.message = 'Update'"); + "Update" + } + } +} + +fn commit_template() -> &'static str { + "Commit template" +} + +pub fn commit_message(repo: &Repository, config: &Value, action: StoreAction) -> Result { + use libimaginteraction::ask::ask_string; + use libimagutil::edit::edit_in_tmpfile_with_command; + use std::process::Command; + if commit_interactive(config) { - unimplemented!() + if commit_with_editor(config) { + 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 = String::from(commit_template()); + 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 { - Ok(String::from("Dummy commit")) + Ok(String::from(commit_default_msg(config))) } } diff --git a/libimagstorestdhook/src/vcs/git/error.rs b/libimagstorestdhook/src/vcs/git/error.rs index a8a00e14..d63c792e 100644 --- a/libimagstorestdhook/src/vcs/git/error.rs +++ b/libimagstorestdhook/src/vcs/git/error.rs @@ -32,7 +32,12 @@ generate_error_module!( StoreIdHandlingError => "Error handling the store id object", StoreIdStripError => "Couldn't strip prefix from StoreID object", - RepositoryFileStatusError => "Error while getting file status" + RepositoryFileStatusError => "Error while getting file status", + + GitConfigFetchError => "Error fetching git config", + GitConfigEditorFetchError => "Error fetching 'editor' from git config", + CommitEditorInstantiationError => "Error when trying to instantiate commit editor", + EditorError => "Error while calling editor" ); ); diff --git a/libimagstorestdhook/src/vcs/git/update.rs b/libimagstorestdhook/src/vcs/git/update.rs index fb270e40..2dce8ab2 100644 --- a/libimagstorestdhook/src/vcs/git/update.rs +++ b/libimagstorestdhook/src/vcs/git/update.rs @@ -167,7 +167,7 @@ impl StoreIdAccessor for UpdateHook { .map_into_hook_error() ); - let message = try!(commit_message(cfg, StoreAction::Update) + let message = try!(commit_message(&repo, cfg, StoreAction::Update) .map_dbg_err_str("Failed to get commit message")); try!(repo.commit(Some("HEAD"), &signature, &signature, &message, &tree, &parents) diff --git a/libimagutil/Cargo.toml b/libimagutil/Cargo.toml index 53e0e0ee..87be5306 100644 --- a/libimagutil/Cargo.toml +++ b/libimagutil/Cargo.toml @@ -7,4 +7,5 @@ authors = ["Matthias Beyer "] lazy_static = "0.1.15" log = "0.3" regex = "0.1" +tempfile = "2.1.1" diff --git a/libimagutil/src/edit.rs b/libimagutil/src/edit.rs new file mode 100644 index 00000000..ad23b6ed --- /dev/null +++ b/libimagutil/src/edit.rs @@ -0,0 +1,36 @@ +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; +use std::io::Write; +use std::process::Command; +use std::io::Error as IOError; + +use tempfile::NamedTempFile; + +pub fn edit_in_tmpfile_with_command(mut cmd: Command, s: &mut String) -> Result { + let file = try!(NamedTempFile::new()); + let file_path = file.path(); + let mut file = try!(file.reopen()); + + try!(file.write_all(&s.clone().into_bytes()[..])); + try!(file.sync_data()); + + cmd.arg(file_path) + .status() + .and_then(|status| { + if status.success() { + file.sync_data() + .and_then(|_| file.seek(SeekFrom::Start(0))) + .and_then(|_| { + let mut new_s = String::new(); + let res = file.read_to_string(&mut new_s); + *s = new_s; + res + }) + .map(|_| true) + } else { + Ok(false) + } + }) +} + diff --git a/libimagutil/src/lib.rs b/libimagutil/src/lib.rs index 16183e7b..c0026c6f 100644 --- a/libimagutil/src/lib.rs +++ b/libimagutil/src/lib.rs @@ -16,9 +16,11 @@ #[macro_use] extern crate lazy_static; #[macro_use] extern crate log; extern crate regex; +extern crate tempfile; #[macro_use] mod log_result; pub mod debug_result; +pub mod edit; pub mod info_result; pub mod ismatch; pub mod iter;