Skip to content

Commit cc22408

Browse files
pm100Stephan Dilly
authored and
Stephan Dilly
committed
adding pre-commit hook (#386)
see #313
1 parent 2d8b481 commit cc22408

File tree

4 files changed

+131
-18
lines changed

4 files changed

+131
-18
lines changed

asyncgit/src/sync/hooks.rs

+95-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::{
99
};
1010

1111
const HOOK_POST_COMMIT: &str = ".git/hooks/post-commit";
12+
const HOOK_PRE_COMMIT: &str = ".git/hooks/pre-commit";
1213
const HOOK_COMMIT_MSG: &str = ".git/hooks/commit-msg";
1314
const HOOK_COMMIT_MSG_TEMP_FILE: &str = ".git/COMMIT_EDITMSG";
1415

@@ -45,6 +46,21 @@ pub fn hooks_commit_msg(
4546
}
4647
}
4748

49+
/// this hook is documented here https://git-scm.com/docs/githooks#_pre_commit
50+
///
51+
pub fn hooks_pre_commit(repo_path: &str) -> Result<HookResult> {
52+
scope_time!("hooks_pre_commit");
53+
54+
let work_dir = work_dir_as_string(repo_path)?;
55+
56+
if hook_runable(work_dir.as_str(), HOOK_PRE_COMMIT) {
57+
let res = run_hook(work_dir.as_str(), HOOK_PRE_COMMIT, &[]);
58+
59+
Ok(res)
60+
} else {
61+
Ok(HookResult::Ok)
62+
}
63+
}
4864
///
4965
pub fn hooks_post_commit(repo_path: &str) -> Result<HookResult> {
5066
scope_time!("hooks_post_commit");
@@ -94,13 +110,8 @@ fn run_hook(
94110
hook_script: &str,
95111
args: &[&str],
96112
) -> HookResult {
97-
let mut bash_args = vec![hook_script.to_string()];
98-
bash_args.extend_from_slice(
99-
&args
100-
.iter()
101-
.map(|x| (*x).to_string())
102-
.collect::<Vec<String>>(),
103-
);
113+
let arg_str = format!("{} {}", hook_script, args.join(" "));
114+
let bash_args = vec!["-c".to_string(), arg_str];
104115

105116
let output = Command::new("bash")
106117
.args(bash_args)
@@ -204,6 +215,83 @@ exit 0
204215
assert_eq!(msg, String::from("test"));
205216
}
206217

218+
#[test]
219+
fn test_pre_commit_sh() {
220+
let (_td, repo) = repo_init().unwrap();
221+
let root = repo.path().parent().unwrap();
222+
let repo_path = root.as_os_str().to_str().unwrap();
223+
224+
let hook = b"#!/bin/sh
225+
exit 0
226+
";
227+
228+
create_hook(root, HOOK_PRE_COMMIT, hook);
229+
let res = hooks_pre_commit(repo_path).unwrap();
230+
assert_eq!(res, HookResult::Ok);
231+
}
232+
233+
#[test]
234+
fn test_pre_commit_fail_sh() {
235+
let (_td, repo) = repo_init().unwrap();
236+
let root = repo.path().parent().unwrap();
237+
let repo_path = root.as_os_str().to_str().unwrap();
238+
239+
let hook = b"#!/bin/sh
240+
echo 'rejected'
241+
exit 1
242+
";
243+
244+
create_hook(root, HOOK_PRE_COMMIT, hook);
245+
let res = hooks_pre_commit(repo_path).unwrap();
246+
assert!(res != HookResult::Ok);
247+
}
248+
249+
#[test]
250+
fn test_pre_commit_py() {
251+
let (_td, repo) = repo_init().unwrap();
252+
let root = repo.path().parent().unwrap();
253+
let repo_path = root.as_os_str().to_str().unwrap();
254+
255+
// mirror how python pre-commmit sets itself up
256+
#[cfg(not(windows))]
257+
let hook = b"#!/usr/bin/env python
258+
import sys
259+
sys.exit(0)
260+
";
261+
#[cfg(windows)]
262+
let hook = b"#!/bin/env python.exe
263+
import sys
264+
sys.exit(0)
265+
";
266+
267+
create_hook(root, HOOK_PRE_COMMIT, hook);
268+
let res = hooks_pre_commit(repo_path).unwrap();
269+
assert_eq!(res, HookResult::Ok);
270+
}
271+
272+
#[test]
273+
fn test_pre_commit_fail_py() {
274+
let (_td, repo) = repo_init().unwrap();
275+
let root = repo.path().parent().unwrap();
276+
let repo_path = root.as_os_str().to_str().unwrap();
277+
278+
// mirror how python pre-commmit sets itself up
279+
#[cfg(not(windows))]
280+
let hook = b"#!/usr/bin/env python
281+
import sys
282+
sys.exit(1)
283+
";
284+
#[cfg(windows)]
285+
let hook = b"#!/bin/env python.exe
286+
import sys
287+
sys.exit(1)
288+
";
289+
290+
create_hook(root, HOOK_PRE_COMMIT, hook);
291+
let res = hooks_pre_commit(repo_path).unwrap();
292+
assert!(res != HookResult::Ok);
293+
}
294+
207295
#[test]
208296
fn test_hooks_commit_msg_reject() {
209297
let (_td, repo) = repo_init().unwrap();

asyncgit/src/sync/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ pub use commit_details::{
3131
pub use commit_files::get_commit_files;
3232
pub use commits_info::{get_commits_info, CommitId, CommitInfo};
3333
pub use diff::get_diff_commit;
34-
pub use hooks::{hooks_commit_msg, hooks_post_commit, HookResult};
34+
pub use hooks::{
35+
hooks_commit_msg, hooks_post_commit, hooks_pre_commit, HookResult,
36+
};
3537
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
3638
pub use ignore::add_to_ignore;
3739
pub use logwalker::LogWalker;

src/components/commit.rs

+10
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ impl CommitComponent {
194194
}
195195

196196
fn commit_msg(&mut self, msg: String) -> Result<()> {
197+
if let HookResult::NotOk(e) = sync::hooks_pre_commit(CWD)? {
198+
log::error!("pre-commit hook error: {}", e);
199+
self.queue.borrow_mut().push_back(
200+
InternalEvent::ShowErrorMsg(format!(
201+
"pre-commit hook error:\n{}",
202+
e
203+
)),
204+
);
205+
return Ok(());
206+
}
197207
let mut msg = msg;
198208
if let HookResult::NotOk(e) =
199209
sync::hooks_commit_msg(CWD, &mut msg)?

src/components/msg.rs

+23-10
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ use super::{
44
};
55
use crate::{keys::SharedKeyConfig, strings, ui};
66
use crossterm::event::Event;
7+
use std::convert::TryFrom;
78
use tui::{
89
backend::Backend,
910
layout::{Alignment, Rect},
10-
text::{Span, Spans},
11+
text::Span,
1112
widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap},
1213
Frame,
1314
};
1415
use ui::style::SharedTheme;
15-
1616
pub struct MsgComponent {
1717
title: String,
1818
msg: String,
@@ -32,17 +32,30 @@ impl DrawableComponent for MsgComponent {
3232
if !self.visible {
3333
return Ok(());
3434
}
35-
let txt = Spans::from(
36-
self.msg
37-
.split('\n')
38-
.map(|string| Span::raw::<String>(string.to_string()))
39-
.collect::<Vec<Span>>(),
40-
);
4135

42-
let area = ui::centered_rect_absolute(65, 25, f.size());
36+
// determine the maximum width of text block
37+
let lens = self
38+
.msg
39+
.split('\n')
40+
.map(str::len)
41+
.collect::<Vec<usize>>();
42+
let mut max = lens.iter().max().expect("max") + 2;
43+
if max > std::u16::MAX as usize {
44+
max = std::u16::MAX as usize;
45+
}
46+
let mut width =
47+
u16::try_from(max).expect("cant fail due to check above");
48+
// dont overflow screen, and dont get too narrow
49+
if width > f.size().width {
50+
width = f.size().width
51+
} else if width < 60 {
52+
width = 60
53+
}
54+
55+
let area = ui::centered_rect_absolute(width, 25, f.size());
4356
f.render_widget(Clear, area);
4457
f.render_widget(
45-
Paragraph::new(txt)
58+
Paragraph::new(self.msg.clone())
4659
.block(
4760
Block::default()
4861
.title(Span::styled(

0 commit comments

Comments
 (0)