support hookspath (#1054)

This commit is contained in:
Stephan Dilly 2022-01-17 15:06:54 +01:00 committed by GitHub
parent 449dc43a5f
commit 435de9cda3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 96 additions and 14 deletions

View File

@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
![delete-tag-remote](assets/delete-tag-remote.gif)
### Added
- support `core.hooksPath` ([#1044](https://github.com/extrawurst/gitui/issues/1044))
- allow reverting a commit from the commit log ([#927](https://github.com/extrawurst/gitui/issues/927))
- disable pull cmd on local-only branches ([#1047](https://github.com/extrawurst/gitui/issues/1047))
- support adding annotations to tags ([#747](https://github.com/extrawurst/gitui/issues/747))

10
Cargo.lock generated
View File

@ -75,6 +75,7 @@ dependencies = [
"rayon-core",
"scopetime",
"serial_test",
"shellexpand",
"tempfile",
"thiserror",
"unicode-truncate",
@ -1219,6 +1220,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
[[package]]
name = "shellexpand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829"
dependencies = [
"dirs-next",
]
[[package]]
name = "signal-hook"
version = "0.3.13"

View File

@ -82,7 +82,6 @@ These are the high level goals before calling out `1.0`:
## 5. <a name="limitations"></a> Known Limitations <small><sup>[Top ▲](#table-of-contents)</sup></small>
- no support for [core.hooksPath](https://git-scm.com/docs/githooks) config (see [#1044](https://github.com/extrawurst/gitui/issues/1044))
- no support for GPG signing (see [#97](https://github.com/extrawurst/gitui/issues/97))
Currently, this tool does not fully substitute the _git shell_, however both tools work well in tandem.

View File

@ -22,6 +22,7 @@ log = "0.4"
openssl-sys = { version = '0.9', features = ["vendored"] }
rayon-core = "1.9"
scopetime = { path = "../scopetime", version = "0.1" }
shellexpand = "2.1"
thiserror = "1.0"
unicode-truncate = "0.2.0"
url = "2.2"

View File

@ -61,6 +61,14 @@ pub enum Error {
///
#[error("EasyCast error:{0}")]
EasyCast(#[from] easy_cast::Error),
///
#[error("shellexpand error:{0}")]
Shell(#[from] shellexpand::LookupError<std::env::VarError>),
///
#[error("path string error")]
PathString,
}
///

View File

@ -1,16 +1,17 @@
use super::{repository::repo, RepoPath};
use crate::error::Result;
use crate::error::{self, Result};
use scopetime::scope_time;
use std::{
fs::File,
io::{Read, Write},
path::{Path, PathBuf},
process::Command,
str::FromStr,
};
const HOOK_POST_COMMIT: &str = "hooks/post-commit";
const HOOK_PRE_COMMIT: &str = "hooks/pre-commit";
const HOOK_COMMIT_MSG: &str = "hooks/commit-msg";
const HOOK_POST_COMMIT: &str = "post-commit";
const HOOK_PRE_COMMIT: &str = "pre-commit";
const HOOK_COMMIT_MSG: &str = "commit-msg";
const HOOK_COMMIT_MSG_TEMP_FILE: &str = "COMMIT_EDITMSG";
struct HookPaths {
@ -26,8 +27,30 @@ impl HookPaths {
.workdir()
.unwrap_or_else(|| repo.path())
.to_path_buf();
let git_dir = repo.path().to_path_buf();
let hook = git_dir.join(hook);
let hooks_path = repo
.config()
.and_then(|config| config.get_string("core.hooksPath"))
.map_or_else(
|e| {
log::error!("hookspath error: {}", e);
repo.path().to_path_buf().join("hooks/")
},
PathBuf::from,
);
let hook = hooks_path.join(hook);
let hook = shellexpand::full(
hook.as_os_str()
.to_str()
.ok_or(error::Error::PathString)?,
)?;
let hook = PathBuf::from_str(hook.as_ref())
.map_err(|_| error::Error::PathString)?;
Ok(Self {
git: git_dir,
hook,
@ -143,10 +166,14 @@ fn is_executable(path: &Path) -> bool {
use std::os::unix::fs::PermissionsExt;
let metadata = match path.metadata() {
Ok(metadata) => metadata,
Err(_) => return false,
Err(e) => {
log::error!("metadata error: {}", e);
return false;
}
};
let permissions = metadata.permissions();
permissions.mode() & 0o111 != 0
}
@ -181,20 +208,28 @@ mod tests {
assert_eq!(res, HookResult::Ok);
}
fn create_hook(path: &RepoPath, hook: &str, hook_script: &[u8]) {
fn create_hook(
path: &RepoPath,
hook: &str,
hook_script: &[u8],
) -> PathBuf {
let hook = HookPaths::new(path, hook).unwrap();
File::create(&hook.hook)
.unwrap()
.write_all(hook_script)
.unwrap();
let path = hook.hook.clone();
create_hook_in_path(&hook.hook, hook_script);
path
}
fn create_hook_in_path(path: &Path, hook_script: &[u8]) {
File::create(path).unwrap().write_all(hook_script).unwrap();
#[cfg(not(windows))]
{
let hook = hook.hook.as_os_str();
Command::new("chmod")
.arg("+x")
.arg(hook)
.arg(path)
// .current_dir(path)
.output()
.unwrap();
@ -255,6 +290,34 @@ exit 1
assert!(res != HookResult::Ok);
}
#[test]
fn test_pre_commit_fail_hookspath() {
let (_td, repo) = repo_init().unwrap();
let root = repo.path().parent().unwrap();
let hooks = TempDir::new().unwrap();
let repo_path: &RepoPath =
&root.as_os_str().to_str().unwrap().into();
let hook = b"#!/bin/sh
echo 'rejected'
exit 1
";
create_hook_in_path(&hooks.path().join("pre-commit"), hook);
repo.config()
.unwrap()
.set_str(
"core.hooksPath",
hooks.path().as_os_str().to_str().unwrap(),
)
.unwrap();
let res = hooks_pre_commit(repo_path).unwrap();
assert_eq!(
res,
HookResult::NotOk(String::from("rejected\n"))
);
}
#[test]
fn test_pre_commit_fail_bare() {
let (git_root, _repo) = repo_init_bare().unwrap();