mirror of
https://github.com/extrawurst/gitui.git
synced 2024-12-26 18:43:37 +03:00
Support prepare commit hook (#1978)
This commit is contained in:
parent
7b7c5c4131
commit
e7c61ffc89
@ -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))
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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
|
||||
|
@ -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::*;
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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()?;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user