mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 08:58:21 +00:00
parent
bbc0488d22
commit
99c3277e94
4 changed files with 131 additions and 18 deletions
|
|
@ -9,6 +9,7 @@ use std::{
|
|||
};
|
||||
|
||||
const HOOK_POST_COMMIT: &str = ".git/hooks/post-commit";
|
||||
const HOOK_PRE_COMMIT: &str = ".git/hooks/pre-commit";
|
||||
const HOOK_COMMIT_MSG: &str = ".git/hooks/commit-msg";
|
||||
const HOOK_COMMIT_MSG_TEMP_FILE: &str = ".git/COMMIT_EDITMSG";
|
||||
|
||||
|
|
@ -45,6 +46,21 @@ pub fn hooks_commit_msg(
|
|||
}
|
||||
}
|
||||
|
||||
/// this hook is documented here https://git-scm.com/docs/githooks#_pre_commit
|
||||
///
|
||||
pub fn hooks_pre_commit(repo_path: &str) -> Result<HookResult> {
|
||||
scope_time!("hooks_pre_commit");
|
||||
|
||||
let work_dir = work_dir_as_string(repo_path)?;
|
||||
|
||||
if hook_runable(work_dir.as_str(), HOOK_PRE_COMMIT) {
|
||||
let res = run_hook(work_dir.as_str(), HOOK_PRE_COMMIT, &[]);
|
||||
|
||||
Ok(res)
|
||||
} else {
|
||||
Ok(HookResult::Ok)
|
||||
}
|
||||
}
|
||||
///
|
||||
pub fn hooks_post_commit(repo_path: &str) -> Result<HookResult> {
|
||||
scope_time!("hooks_post_commit");
|
||||
|
|
@ -94,13 +110,8 @@ fn run_hook(
|
|||
hook_script: &str,
|
||||
args: &[&str],
|
||||
) -> HookResult {
|
||||
let mut bash_args = vec![hook_script.to_string()];
|
||||
bash_args.extend_from_slice(
|
||||
&args
|
||||
.iter()
|
||||
.map(|x| (*x).to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
let arg_str = format!("{} {}", hook_script, args.join(" "));
|
||||
let bash_args = vec!["-c".to_string(), arg_str];
|
||||
|
||||
let output = Command::new("bash")
|
||||
.args(bash_args)
|
||||
|
|
@ -204,6 +215,83 @@ exit 0
|
|||
assert_eq!(msg, String::from("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_commit_sh() {
|
||||
let (_td, repo) = repo_init().unwrap();
|
||||
let root = repo.path().parent().unwrap();
|
||||
let repo_path = root.as_os_str().to_str().unwrap();
|
||||
|
||||
let hook = b"#!/bin/sh
|
||||
exit 0
|
||||
";
|
||||
|
||||
create_hook(root, HOOK_PRE_COMMIT, hook);
|
||||
let res = hooks_pre_commit(repo_path).unwrap();
|
||||
assert_eq!(res, HookResult::Ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_commit_fail_sh() {
|
||||
let (_td, repo) = repo_init().unwrap();
|
||||
let root = repo.path().parent().unwrap();
|
||||
let repo_path = root.as_os_str().to_str().unwrap();
|
||||
|
||||
let hook = b"#!/bin/sh
|
||||
echo 'rejected'
|
||||
exit 1
|
||||
";
|
||||
|
||||
create_hook(root, HOOK_PRE_COMMIT, hook);
|
||||
let res = hooks_pre_commit(repo_path).unwrap();
|
||||
assert!(res != HookResult::Ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_commit_py() {
|
||||
let (_td, repo) = repo_init().unwrap();
|
||||
let root = repo.path().parent().unwrap();
|
||||
let repo_path = root.as_os_str().to_str().unwrap();
|
||||
|
||||
// mirror how python pre-commmit sets itself up
|
||||
#[cfg(not(windows))]
|
||||
let hook = b"#!/usr/bin/env python
|
||||
import sys
|
||||
sys.exit(0)
|
||||
";
|
||||
#[cfg(windows)]
|
||||
let hook = b"#!/bin/env python.exe
|
||||
import sys
|
||||
sys.exit(0)
|
||||
";
|
||||
|
||||
create_hook(root, HOOK_PRE_COMMIT, hook);
|
||||
let res = hooks_pre_commit(repo_path).unwrap();
|
||||
assert_eq!(res, HookResult::Ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_commit_fail_py() {
|
||||
let (_td, repo) = repo_init().unwrap();
|
||||
let root = repo.path().parent().unwrap();
|
||||
let repo_path = root.as_os_str().to_str().unwrap();
|
||||
|
||||
// mirror how python pre-commmit sets itself up
|
||||
#[cfg(not(windows))]
|
||||
let hook = b"#!/usr/bin/env python
|
||||
import sys
|
||||
sys.exit(1)
|
||||
";
|
||||
#[cfg(windows)]
|
||||
let hook = b"#!/bin/env python.exe
|
||||
import sys
|
||||
sys.exit(1)
|
||||
";
|
||||
|
||||
create_hook(root, HOOK_PRE_COMMIT, hook);
|
||||
let res = hooks_pre_commit(repo_path).unwrap();
|
||||
assert!(res != HookResult::Ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hooks_commit_msg_reject() {
|
||||
let (_td, repo) = repo_init().unwrap();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ pub use commit_details::{
|
|||
pub use commit_files::get_commit_files;
|
||||
pub use commits_info::{get_commits_info, CommitId, CommitInfo};
|
||||
pub use diff::get_diff_commit;
|
||||
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
|
||||
pub use hooks::{
|
||||
hooks_commit_msg, hooks_post_commit, hooks_pre_commit, HookResult,
|
||||
};
|
||||
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
|
||||
pub use ignore::add_to_ignore;
|
||||
pub use logwalker::LogWalker;
|
||||
|
|
|
|||
|
|
@ -194,6 +194,16 @@ impl CommitComponent {
|
|||
}
|
||||
|
||||
fn commit_msg(&mut self, msg: String) -> Result<()> {
|
||||
if let HookResult::NotOk(e) = sync::hooks_pre_commit(CWD)? {
|
||||
log::error!("pre-commit hook error: {}", e);
|
||||
self.queue.borrow_mut().push_back(
|
||||
InternalEvent::ShowErrorMsg(format!(
|
||||
"pre-commit hook error:\n{}",
|
||||
e
|
||||
)),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let mut msg = msg;
|
||||
if let HookResult::NotOk(e) =
|
||||
sync::hooks_commit_msg(CWD, &mut msg)?
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@ use super::{
|
|||
};
|
||||
use crate::{keys::SharedKeyConfig, strings, ui};
|
||||
use crossterm::event::Event;
|
||||
use std::convert::TryFrom;
|
||||
use tui::{
|
||||
backend::Backend,
|
||||
layout::{Alignment, Rect},
|
||||
text::{Span, Spans},
|
||||
text::Span,
|
||||
widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap},
|
||||
Frame,
|
||||
};
|
||||
use ui::style::SharedTheme;
|
||||
|
||||
pub struct MsgComponent {
|
||||
title: String,
|
||||
msg: String,
|
||||
|
|
@ -32,17 +32,30 @@ impl DrawableComponent for MsgComponent {
|
|||
if !self.visible {
|
||||
return Ok(());
|
||||
}
|
||||
let txt = Spans::from(
|
||||
self.msg
|
||||
.split('\n')
|
||||
.map(|string| Span::raw::<String>(string.to_string()))
|
||||
.collect::<Vec<Span>>(),
|
||||
);
|
||||
|
||||
let area = ui::centered_rect_absolute(65, 25, f.size());
|
||||
// determine the maximum width of text block
|
||||
let lens = self
|
||||
.msg
|
||||
.split('\n')
|
||||
.map(str::len)
|
||||
.collect::<Vec<usize>>();
|
||||
let mut max = lens.iter().max().expect("max") + 2;
|
||||
if max > std::u16::MAX as usize {
|
||||
max = std::u16::MAX as usize;
|
||||
}
|
||||
let mut width =
|
||||
u16::try_from(max).expect("cant fail due to check above");
|
||||
// dont overflow screen, and dont get too narrow
|
||||
if width > f.size().width {
|
||||
width = f.size().width
|
||||
} else if width < 60 {
|
||||
width = 60
|
||||
}
|
||||
|
||||
let area = ui::centered_rect_absolute(width, 25, f.size());
|
||||
f.render_widget(Clear, area);
|
||||
f.render_widget(
|
||||
Paragraph::new(txt)
|
||||
Paragraph::new(self.msg.clone())
|
||||
.block(
|
||||
Block::default()
|
||||
.title(Span::styled(
|
||||
|
|
|
|||
Loading…
Reference in a new issue