diff options
author | extrawurst <776816+extrawurst@users.noreply.github.com> | 2023-12-16 00:07:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-16 00:07:54 +0100 |
commit | e7c61ffc89f987fa3f6a1dbb6e7c6f6a83a0b321 (patch) | |
tree | f3a86f9022cc823c1f38b9747782ab170f55d14e | |
parent | 7b7c5c41315c0c6e061bfd62374c54749984b206 (diff) |
Support prepare commit hook (#1978)
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | asyncgit/src/sync/hooks.rs | 17 | ||||
-rw-r--r-- | asyncgit/src/sync/mod.rs | 3 | ||||
-rw-r--r-- | git2-hooks/Cargo.toml | 2 | ||||
-rw-r--r-- | git2-hooks/src/lib.rs | 121 | ||||
-rw-r--r-- | src/components/commit.rs | 36 |
8 files changed, 171 insertions, 13 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e2589b..af52455f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * `theme.ron` now supports customizing line break symbol ([#1894](https://github.com/extrawurst/gitui/issues/1894)) * add confirmation for dialog for undo commit [[@TeFiLeDo](https://github.com/TeFiLeDo)] ([#1912](https://github.com/extrawurst/gitui/issues/1912)) +* support `prepare-commit-msg` hook ([#1873](https://github.com/extrawurst/gitui/issues/1873)) ### Changed * do not allow tag when `tag.gpgsign` enabled [[@TeFiLeDo](https://github.com/TeFiLeDo)] ([#1915](https://github.com/extrawurst/gitui/pull/1915)) @@ -714,7 +714,7 @@ dependencies = [ [[package]] name = "git2-hooks" -version = "0.3.0" +version = "0.3.1" dependencies = [ "git2", "git2-testing", @@ -43,7 +43,7 @@ - Fast and intuitive **keyboard only** control - Context based help (**no need to memorize** tons of hot-keys) -- Inspect, commit, and amend changes (incl. hooks: *pre-commit*,*commit-msg*,*post-commit*) +- Inspect, commit, and amend changes (incl. hooks: *pre-commit*,*commit-msg*,*post-commit*,*prepare-commit-msg*) - Stage, unstage, revert and reset files, hunks and lines - Stashing (save, pop, apply, drop, and inspect) - Push / Fetch to / from remote diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index f480da07..b6928918 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -1,5 +1,6 @@ use super::{repository::repo, RepoPath}; use crate::error::Result; +pub use git2_hooks::PrepareCommitMsgSource; use scopetime::scope_time; /// @@ -59,6 +60,22 @@ pub fn hooks_post_commit(repo_path: &RepoPath) -> Result<HookResult> { Ok(git2_hooks::hooks_post_commit(&repo, None)?.into()) } +/// +pub fn hooks_prepare_commit_msg( + repo_path: &RepoPath, + source: PrepareCommitMsgSource, + msg: &mut String, +) -> Result<HookResult> { + scope_time!("hooks_prepare_commit_msg"); + + let repo = repo(repo_path)?; + + Ok(git2_hooks::hooks_prepare_commit_msg( + &repo, None, source, msg, + )? + .into()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 909bea6f..83683a9e 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -65,7 +65,8 @@ pub use config::{ pub use diff::get_diff_commit; pub use git2::BranchType; pub use hooks::{ - hooks_commit_msg, hooks_post_commit, hooks_pre_commit, HookResult, + hooks_commit_msg, hooks_post_commit, hooks_pre_commit, + hooks_prepare_commit_msg, HookResult, PrepareCommitMsgSource, }; pub use hunks::{reset_hunk, stage_hunk, unstage_hunk}; pub use ignore::add_to_ignore; diff --git a/git2-hooks/Cargo.toml b/git2-hooks/Cargo.toml index c85f4cc9..1c6c63e5 100644 --- a/git2-hooks/Cargo.toml +++ b/git2-hooks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git2-hooks" -version = "0.3.0" +version = "0.3.1" authors = ["extrawurst <mail@rusticorn.com>"] edition = "2021" description = "adds git hooks support based on git2-rs" diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index 0cf5ac8d..ea7842cc 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -27,6 +27,7 @@ use git2::Repository; pub const HOOK_POST_COMMIT: &str = "post-commit"; pub const HOOK_PRE_COMMIT: &str = "pre-commit"; pub const HOOK_COMMIT_MSG: &str = "commit-msg"; +pub const HOOK_PREPARE_COMMIT_MSG: &str = "prepare-commit-msg"; const HOOK_COMMIT_MSG_TEMP_FILE: &str = "COMMIT_EDITMSG"; @@ -152,6 +153,65 @@ pub fn hooks_post_commit( hook.run_hook(&[]) } +/// +pub enum PrepareCommitMsgSource { + Message, + Template, + Merge, + Squash, + Commit(git2::Oid), +} + +/// this hook is documented here <https://git-scm.com/docs/githooks#_prepare_commit_msg> +pub fn hooks_prepare_commit_msg( + repo: &Repository, + other_paths: Option<&[&str]>, + source: PrepareCommitMsgSource, + msg: &mut String, +) -> Result<HookResult> { + let hook = + HookPaths::new(repo, other_paths, HOOK_PREPARE_COMMIT_MSG)?; + + if !hook.found() { + return Ok(HookResult::NoHookFound); + } + + let temp_file = hook.git.join(HOOK_COMMIT_MSG_TEMP_FILE); + File::create(&temp_file)?.write_all(msg.as_bytes())?; + + let temp_file_path = temp_file.as_os_str().to_string_lossy(); + + let vec = vec![ + temp_file_path.as_ref(), + match source { + PrepareCommitMsgSource::Message => "message", + PrepareCommitMsgSource::Template => "template", + PrepareCommitMsgSource::Merge => "merge", + PrepareCommitMsgSource::Squash => "squash", + PrepareCommitMsgSource::Commit(_) => "commit", + }, + ]; + let mut args = vec; + + let id = if let PrepareCommitMsgSource::Commit(id) = &source { + Some(id.to_string()) + } else { + None + }; + + if let Some(id) = &id { + args.push(id); + } + + let res = hook.run_hook(args.as_slice())?; + + // load possibly altered msg + msg.clear(); + File::open(temp_file)?.read_to_string(msg)?; + + Ok(res) +} + #[cfg(test)] mod tests { use super::*; @@ -480,4 +540,65 @@ exit 0 assert_eq!(hook.pwd, git_root.parent().unwrap()); } + + #[test] + fn test_hooks_prep_commit_msg_success() { + let (_td, repo) = repo_init(); + + let hook = b"#!/bin/sh +echo msg:$2 > $1 +exit 0 + "; + + create_hook(&repo, HOOK_PREPARE_COMMIT_MSG, hook); + + let mut msg = String::from("test"); + let res = hooks_prepare_commit_msg( + &repo, + None, + PrepareCommitMsgSource::Message, + &mut msg, + ) + .unwrap(); + + assert!(matches!(res, HookResult::Ok { .. })); + assert_eq!(msg, String::from("msg:message\n")); + } + + #[test] + fn test_hooks_prep_commit_msg_reject() { + let (_td, repo) = repo_init(); + + let hook = b"#!/bin/sh +echo $2,$3 > $1 +echo 'rejected' +exit 2 + "; + + create_hook(&repo, HOOK_PREPARE_COMMIT_MSG, hook); + + let mut msg = String::from("test"); + let res = hooks_prepare_commit_msg( + &repo, + None, + PrepareCommitMsgSource::Commit(git2::Oid::zero()), + &mut msg, + ) + .unwrap(); + + let HookResult::RunNotSuccessful { code, stdout, .. } = res + else { + unreachable!() + }; + + assert_eq!(code.unwrap(), 2); + assert_eq!(&stdout, "rejected\n"); + + assert_eq!( + msg, + String::from( + "commit,0000000000000000000000000000000000000000\n" + ) + ); + } } diff --git a/src/components/commit.rs b/src/components/commit.rs index ba32394a..bd762878 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -14,8 +14,8 @@ use anyhow::{bail, Ok, Result}; use asyncgit::{ cached, message_prettify, sync::{ - self, get_config_string, CommitId, HookResult, RepoPathRef, - RepoState, + self, get_config_string, CommitId, HookResult, + PrepareCommitMsgSource, RepoPathRef, RepoState, }, StatusItem, StatusItemType, }; @@ -366,7 +366,7 @@ impl CommitComponent { let repo_state = sync::repo_state(&self.repo.borrow())?; - self.mode = if repo_state != RepoState::Clean + let (mode, msg_source) = if repo_state != RepoState::Clean && reword.is_some() { bail!("cannot reword while repo is not in a clean state"); @@ -381,7 +381,7 @@ impl CommitComponent { .combine(), ); self.input.set_title(strings::commit_reword_title()); - Mode::Reword(reword_id) + (Mode::Reword(reword_id), PrepareCommitMsgSource::Message) } else { match repo_state { RepoState::Merge => { @@ -392,7 +392,7 @@ impl CommitComponent { self.input.set_text(sync::merge_msg( &self.repo.borrow(), )?); - Mode::Merge(ids) + (Mode::Merge(ids), PrepareCommitMsgSource::Merge) } RepoState::Revert => { self.input @@ -400,7 +400,7 @@ impl CommitComponent { self.input.set_text(sync::merge_msg( &self.repo.borrow(), )?); - Mode::Revert + (Mode::Revert, PrepareCommitMsgSource::Message) } _ => { @@ -430,17 +430,35 @@ impl CommitComponent { .ok() }); - if self.is_empty() { + let msg_source = if self.is_empty() { if let Some(s) = &self.commit_template { self.input.set_text(s.clone()); + PrepareCommitMsgSource::Template + } else { + PrepareCommitMsgSource::Message } - } + } else { + PrepareCommitMsgSource::Message + }; self.input.set_title(strings::commit_title()); - Mode::Normal + + (Mode::Normal, msg_source) } } }; + self.mode = mode; + + let mut msg = self.input.get_text().to_string(); + if let HookResult::NotOk(e) = sync::hooks_prepare_commit_msg( + &self.repo.borrow(), + msg_source, + &mut msg, + )? { + log::error!("prepare-commit-msg hook rejection: {e}",); + } + self.input.set_text(msg); + self.commit_msg_history_idx = 0; self.input.show()?; |