adding pre-commit hook (#386)

see #313
This commit is contained in:
pm100 2020-10-31 17:06:48 -07:00 committed by GitHub
parent bbc0488d22
commit 99c3277e94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 131 additions and 18 deletions

View file

@ -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();

View file

@ -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;

View file

@ -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)?

View file

@ -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(