Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions asyncgit/src/sync/hooks.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{repository::repo, RepoPath};
use crate::error::Result;
pub use git2_hooks::PrepareCommitMsgSource;
use scopetime::scope_time;

///
Expand Down Expand Up @@ -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::*;
Expand Down
3 changes: 2 additions & 1 deletion asyncgit/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion git2-hooks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "git2-hooks"
version = "0.3.0"
version = "0.3.1"
authors = ["extrawurst <[email protected]>"]
edition = "2021"
description = "adds git hooks support based on git2-rs"
Expand Down
121 changes: 121 additions & 0 deletions git2-hooks/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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::*;
Expand Down Expand Up @@ -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"
)
);
}
}
36 changes: 27 additions & 9 deletions src/components/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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");
Expand All @@ -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 => {
Expand All @@ -392,15 +392,15 @@ impl CommitComponent {
self.input.set_text(sync::merge_msg(
&self.repo.borrow(),
)?);
Mode::Merge(ids)
(Mode::Merge(ids), PrepareCommitMsgSource::Merge)
}
RepoState::Revert => {
self.input
.set_title(strings::commit_title_revert());
self.input.set_text(sync::merge_msg(
&self.repo.borrow(),
)?);
Mode::Revert
(Mode::Revert, PrepareCommitMsgSource::Message)
}

_ => {
Expand Down Expand Up @@ -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()?;

Expand Down