mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 01:18:21 +00:00
support hooks: post-commit,commit-msg (#15)
* support hooks: post-commit and commit-msg * some unittests * exclude tests on windows for now
This commit is contained in:
parent
d0fce2dce4
commit
36ff0be9d1
7 changed files with 205 additions and 6 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -24,6 +24,7 @@ version = "0.1.3"
|
|||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"git2",
|
||||
"is_executable",
|
||||
"log",
|
||||
"rayon-core",
|
||||
"scopetime",
|
||||
|
|
@ -351,6 +352,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_executable"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d553b8abc8187beb7d663e34c065ac4570b273bc9511a50e940e99409c577"
|
||||
dependencies = [
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.8.2"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ blazing fast terminal-ui for git written in rust
|
|||
|
||||
* fast and intuitive key only control
|
||||
* context based help (**no** need to remember any hot-key)
|
||||
* inspect/commit changes
|
||||
* inspect/commit changes (incl. hooks: commit-msg/post-commit)
|
||||
* (un)stage files, revert/reset files
|
||||
* scalable ui layout
|
||||
* async [input polling](assets/perf_compare.jpg) and
|
||||
|
|
@ -58,8 +58,7 @@ GITUI_LOGGING=true gitui
|
|||
|
||||
# todo for 0.2 (first release)
|
||||
|
||||
* [ ] support commit-msg hook
|
||||
* [ ] support post-commit hook
|
||||
* [ ] visualize commit-msg hook result
|
||||
* [ ] publish as homebrew-tap
|
||||
|
||||
# inspiration
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ git2 = { git = "https://github.com/rust-lang/git2-rs.git", rev = "617499d7fcf315
|
|||
rayon-core = "1.7"
|
||||
crossbeam-channel = "0.4"
|
||||
log = "0.4"
|
||||
is_executable = "0.1"
|
||||
scopetime = { path = "../scopetime", version = "0.1" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.1"
|
||||
176
asyncgit/src/sync/hooks.rs
Normal file
176
asyncgit/src/sync/hooks.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
use is_executable::IsExecutable;
|
||||
use scopetime::scope_time;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
process::Command,
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
const HOOK_POST_COMMIT: &str = ".git/hooks/post-commit";
|
||||
const HOOK_COMMIT_MSG: &str = ".git/hooks/commit-msg";
|
||||
|
||||
///
|
||||
pub fn hooks_commit_msg(
|
||||
repo_path: &str,
|
||||
msg: &mut String,
|
||||
) -> HookResult {
|
||||
scope_time!("hooks_commit_msg");
|
||||
|
||||
if hook_runable(repo_path, HOOK_COMMIT_MSG) {
|
||||
let mut file = NamedTempFile::new().unwrap();
|
||||
|
||||
write!(file, "{}", msg).unwrap();
|
||||
|
||||
let file_path = file.path().to_str().unwrap();
|
||||
|
||||
dbg!(&file_path);
|
||||
|
||||
let res = run_hook(repo_path, HOOK_COMMIT_MSG, &[&file_path]);
|
||||
|
||||
if let HookResult::NotOk(e) = res {
|
||||
let mut file = file.reopen().unwrap();
|
||||
msg.clear();
|
||||
file.read_to_string(msg).unwrap();
|
||||
HookResult::NotOk(e)
|
||||
} else {
|
||||
HookResult::Ok
|
||||
}
|
||||
} else {
|
||||
HookResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn hooks_post_commit(repo_path: &str) -> HookResult {
|
||||
scope_time!("hooks_post_commit");
|
||||
|
||||
if hook_runable(repo_path, HOOK_POST_COMMIT) {
|
||||
run_hook(repo_path, HOOK_POST_COMMIT, &[])
|
||||
} else {
|
||||
HookResult::Ok
|
||||
}
|
||||
}
|
||||
|
||||
fn hook_runable(path: &str, hook: &str) -> bool {
|
||||
let path = Path::new(path);
|
||||
let path = path.join(hook);
|
||||
|
||||
path.exists() && path.is_executable()
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum HookResult {
|
||||
/// Everything went fine
|
||||
Ok,
|
||||
/// Hook returned error
|
||||
NotOk(String),
|
||||
}
|
||||
|
||||
fn run_hook(path: &str, cmd: &str, args: &[&str]) -> HookResult {
|
||||
let output =
|
||||
Command::new(cmd).args(args).current_dir(path).output();
|
||||
|
||||
let output = output.expect("general hook error");
|
||||
|
||||
if output.status.success() {
|
||||
HookResult::Ok
|
||||
} else {
|
||||
let err = String::from_utf8(output.stderr).unwrap();
|
||||
let out = String::from_utf8(output.stdout).unwrap();
|
||||
let formatted = format!("{}{}", out, err);
|
||||
|
||||
HookResult::NotOk(formatted)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::sync::tests::repo_init;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn test_smoke() {
|
||||
let (_td, repo) = repo_init();
|
||||
let root = repo.path().parent().unwrap();
|
||||
let repo_path = root.as_os_str().to_str().unwrap();
|
||||
|
||||
let mut msg = String::from("test");
|
||||
let res = hooks_commit_msg(repo_path, &mut msg);
|
||||
|
||||
assert_eq!(res, HookResult::Ok);
|
||||
|
||||
let res = hooks_post_commit(repo_path);
|
||||
|
||||
assert_eq!(res, HookResult::Ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn test_hooks_commit_msg_ok() {
|
||||
let (_td, repo) = repo_init();
|
||||
let root = repo.path().parent().unwrap();
|
||||
let repo_path = root.as_os_str().to_str().unwrap();
|
||||
|
||||
let hook = b"
|
||||
#!/bin/sh
|
||||
exit 0
|
||||
";
|
||||
|
||||
File::create(&root.join(HOOK_COMMIT_MSG))
|
||||
.unwrap()
|
||||
.write_all(hook)
|
||||
.unwrap();
|
||||
|
||||
Command::new("chmod")
|
||||
.args(&["+x", HOOK_COMMIT_MSG])
|
||||
.current_dir(root)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let mut msg = String::from("test");
|
||||
let res = hooks_commit_msg(repo_path, &mut msg);
|
||||
|
||||
assert_eq!(res, HookResult::Ok);
|
||||
|
||||
assert_eq!(msg, String::from("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
fn test_hooks_commit_msg() {
|
||||
let (_td, repo) = repo_init();
|
||||
let root = repo.path().parent().unwrap();
|
||||
let repo_path = root.as_os_str().to_str().unwrap();
|
||||
|
||||
let hook = b"
|
||||
#!/bin/sh
|
||||
echo 'msg' > $1
|
||||
echo 'rejected'
|
||||
exit 1
|
||||
";
|
||||
|
||||
File::create(&root.join(HOOK_COMMIT_MSG))
|
||||
.unwrap()
|
||||
.write_all(hook)
|
||||
.unwrap();
|
||||
|
||||
Command::new("chmod")
|
||||
.args(&["+x", HOOK_COMMIT_MSG])
|
||||
.current_dir(root)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let mut msg = String::from("test");
|
||||
let res = hooks_commit_msg(repo_path, &mut msg);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
HookResult::NotOk(String::from("rejected\n"))
|
||||
);
|
||||
|
||||
assert_eq!(msg, String::from("msg\n"));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
//! sync git api
|
||||
|
||||
pub mod diff;
|
||||
mod hooks;
|
||||
mod hunks;
|
||||
mod reset;
|
||||
pub mod status;
|
||||
pub mod utils;
|
||||
|
||||
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
|
||||
pub use hunks::{stage_hunk, unstage_hunk};
|
||||
pub use reset::{reset_stage, reset_workdir};
|
||||
pub use utils::{commit, stage_add};
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ pub fn repo(repo_path: &str) -> Repository {
|
|||
repo
|
||||
}
|
||||
|
||||
///
|
||||
/// this does not run any git hooks
|
||||
pub fn commit(repo_path: &str, msg: &str) {
|
||||
scope_time!("commit");
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ use super::{
|
|||
use crate::{keys, strings, ui};
|
||||
use asyncgit::{sync, CWD};
|
||||
use crossterm::event::{Event, KeyCode};
|
||||
use log::error;
|
||||
use std::borrow::Cow;
|
||||
use strings::commands;
|
||||
use sync::HookResult;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Rect},
|
||||
|
|
@ -121,7 +123,18 @@ impl Component for CommitComponent {
|
|||
|
||||
impl CommitComponent {
|
||||
fn commit(&mut self) {
|
||||
if let HookResult::NotOk(e) =
|
||||
sync::hooks_commit_msg(CWD, &mut self.msg)
|
||||
{
|
||||
error!("commit-msg hook error: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
sync::commit(CWD, &self.msg);
|
||||
if let HookResult::NotOk(e) = sync::hooks_post_commit(CWD) {
|
||||
error!("post-commit hook error: {}", e);
|
||||
}
|
||||
|
||||
self.msg.clear();
|
||||
|
||||
self.hide();
|
||||
|
|
|
|||
Loading…
Reference in a new issue