Skip to content

Commit cb17cfe

Browse files
xlai89extrawurst
andauthored
feat: support pre-push hooks (#2737)
Co-authored-by: extrawurst <[email protected]>
1 parent 2374e00 commit cb17cfe

File tree

6 files changed

+90
-5
lines changed

6 files changed

+90
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
* increase MSRV from 1.81 to 1.82 [[@cruessler](https://github.com/cruessler)]
1111

1212
### Added
13+
* Support pre-push hook [[@xlai89](https://github.com/xlai89)] ([#1933](https://github.com/extrawurst/gitui/issues/1933))
1314
* Message tab supports pageUp and pageDown [[@xlai89](https://github.com/xlai89)] ([#2623](https://github.com/extrawurst/gitui/issues/2623))
1415
* Files and status tab support pageUp and pageDown [[@fatpandac](https://github.com/fatpandac)] ([#1951](https://github.com/extrawurst/gitui/issues/1951))
1516
* support loading custom syntax highlighting themes from a file [[@acuteenvy](https://github.com/acuteenvy)] ([#2565](https://github.com/gitui-org/gitui/pull/2565))

asyncgit/src/sync/hooks.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ pub fn hooks_prepare_commit_msg(
7272
.into())
7373
}
7474

75+
/// see `git2_hooks::hooks_pre_push`
76+
pub fn hooks_pre_push(repo_path: &RepoPath) -> Result<HookResult> {
77+
scope_time!("hooks_pre_push");
78+
79+
let repo = repo(repo_path)?;
80+
81+
Ok(git2_hooks::hooks_pre_push(&repo, None)?.into())
82+
}
83+
7584
#[cfg(test)]
7685
mod tests {
7786
use std::{ffi::OsString, io::Write as _, path::Path};

asyncgit/src/sync/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ pub use diff::get_diff_commit;
6767
pub use git2::BranchType;
6868
pub use hooks::{
6969
hooks_commit_msg, hooks_post_commit, hooks_pre_commit,
70-
hooks_prepare_commit_msg, HookResult, PrepareCommitMsgSource,
70+
hooks_pre_push, hooks_prepare_commit_msg, HookResult,
71+
PrepareCommitMsgSource,
7172
};
7273
pub use hunks::{reset_hunk, stage_hunk, unstage_hunk};
7374
pub use ignore::add_to_ignore;

git2-hooks/src/lib.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub const HOOK_POST_COMMIT: &str = "post-commit";
4444
pub const HOOK_PRE_COMMIT: &str = "pre-commit";
4545
pub const HOOK_COMMIT_MSG: &str = "commit-msg";
4646
pub const HOOK_PREPARE_COMMIT_MSG: &str = "prepare-commit-msg";
47+
pub const HOOK_PRE_PUSH: &str = "pre-push";
4748

4849
const HOOK_COMMIT_MSG_TEMP_FILE: &str = "COMMIT_EDITMSG";
4950

@@ -170,6 +171,20 @@ pub fn hooks_post_commit(
170171
hook.run_hook(&[])
171172
}
172173

174+
/// this hook is documented here <https://git-scm.com/docs/githooks#_pre_push>
175+
pub fn hooks_pre_push(
176+
repo: &Repository,
177+
other_paths: Option<&[&str]>,
178+
) -> Result<HookResult> {
179+
let hook = HookPaths::new(repo, other_paths, HOOK_PRE_PUSH)?;
180+
181+
if !hook.found() {
182+
return Ok(HookResult::NoHookFound);
183+
}
184+
185+
hook.run_hook(&[])
186+
}
187+
173188
pub enum PrepareCommitMsgSource {
174189
Message,
175190
Template,
@@ -658,4 +673,37 @@ exit 2
658673
)
659674
);
660675
}
676+
677+
#[test]
678+
fn test_pre_push_sh() {
679+
let (_td, repo) = repo_init();
680+
681+
let hook = b"#!/bin/sh
682+
exit 0
683+
";
684+
685+
create_hook(&repo, HOOK_PRE_PUSH, hook);
686+
687+
let res = hooks_pre_push(&repo, None).unwrap();
688+
689+
assert!(matches!(res, HookResult::Ok { .. }));
690+
}
691+
692+
#[test]
693+
fn test_pre_push_fail_sh() {
694+
let (_td, repo) = repo_init();
695+
696+
let hook = b"#!/bin/sh
697+
echo 'failed'
698+
exit 3
699+
";
700+
create_hook(&repo, HOOK_PRE_PUSH, hook);
701+
let res = hooks_pre_push(&repo, None).unwrap();
702+
let HookResult::RunNotSuccessful { code, stdout, .. } = res
703+
else {
704+
unreachable!()
705+
};
706+
assert_eq!(code.unwrap(), 3);
707+
assert_eq!(&stdout, "failed\n");
708+
}
661709
}

src/popups/push.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ use asyncgit::{
1616
extract_username_password_for_push,
1717
need_username_password_for_push, BasicAuthCredential,
1818
},
19-
get_branch_remote,
19+
get_branch_remote, hooks_pre_push,
2020
remotes::get_default_remote_for_push,
21-
RepoPathRef,
21+
HookResult, RepoPathRef,
2222
},
2323
AsyncGitNotification, AsyncPush, PushRequest, PushType,
2424
RemoteProgress, RemoteProgressState,
@@ -144,6 +144,19 @@ impl PushPopup {
144144
remote
145145
};
146146

147+
// run pre push hook - can reject push
148+
if let HookResult::NotOk(e) =
149+
hooks_pre_push(&self.repo.borrow())?
150+
{
151+
log::error!("pre-push hook failed: {e}");
152+
self.queue.push(InternalEvent::ShowErrorMsg(format!(
153+
"pre-push hook failed:\n{e}"
154+
)));
155+
self.pending = false;
156+
self.visible = false;
157+
return Ok(());
158+
}
159+
147160
self.pending = true;
148161
self.progress = None;
149162
self.git_push.request(PushRequest {

src/popups/push_tags.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use asyncgit::{
1616
extract_username_password, need_username_password,
1717
BasicAuthCredential,
1818
},
19-
get_default_remote, AsyncProgress, PushTagsProgress,
20-
RepoPathRef,
19+
get_default_remote, hooks_pre_push, AsyncProgress,
20+
HookResult, PushTagsProgress, RepoPathRef,
2121
},
2222
AsyncGitNotification, AsyncPushTags, PushTagsRequest,
2323
};
@@ -84,6 +84,19 @@ impl PushTagsPopup {
8484
&mut self,
8585
cred: Option<BasicAuthCredential>,
8686
) -> Result<()> {
87+
// run pre push hook - can reject push
88+
if let HookResult::NotOk(e) =
89+
hooks_pre_push(&self.repo.borrow())?
90+
{
91+
log::error!("pre-push hook failed: {e}");
92+
self.queue.push(InternalEvent::ShowErrorMsg(format!(
93+
"pre-push hook failed:\n{e}"
94+
)));
95+
self.pending = false;
96+
self.visible = false;
97+
return Ok(());
98+
}
99+
87100
self.pending = true;
88101
self.progress = None;
89102
self.git_push.request(PushTagsRequest {

0 commit comments

Comments
 (0)