Skip to content

Commit cda17c7

Browse files
author
Naseschwarz
committed
Resolve core.hooksPath relative to GIT_WORK_TREE
git supports relative values in core.hooksPath. `man git-config`: > A relative path is taken as relative to the directory where the hooks are > run (see the "DESCRIPTION" section of githooks[5]). `man githooks`: > Before Git invokes a hook, it changes its working directory to either > $GIT_DIR in a bare repository or the root of the working tree in a > > non-bare repository. I.e. relative paths in core.hooksPath in non-bare repositories are always relative to GIT_WORK_TREE. There is a further exception; I believe this is not considered for path resolution: > An exception are hooks triggered during a push (pre-receive, update, > post-receive, post-update, push-to-checkout) which are always executed > in $GIT_DIR.
1 parent a015dc5 commit cda17c7

File tree

4 files changed

+111
-26
lines changed

4 files changed

+111
-26
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
* push: respect `branch.*.merge` when push default is upstream [[@vlad-anger](https://github.com/vlad-anger)] ([#2542](https://github.com/gitui-org/gitui/pull/2542))
2121
* set the terminal title to `gitui ({repo_path})` [[@acuteenvy](https://github.com/acuteenvy)] ([#2462](https://github.com/gitui-org/gitui/issues/2462))
2222

23+
### Fixes
24+
* Resolve `core.hooksPath` relative to `GIT_WORK_TREE` [[@naseschwarz](https://github.com/naseschwarz)] ([#2571](https://github.com/gitui-org/gitui/issues/2571))
25+
2326
## [0.27.0] - 2024-01-14
2427

2528
**new: manage remotes**

asyncgit/src/sync/hooks.rs

+38-15
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ pub fn hooks_prepare_commit_msg(
7676
mod tests {
7777
use super::*;
7878
use crate::sync::tests::repo_init;
79+
use git2_hooks::test_utils::create_hook;
80+
use git2_testing::*;
7981

8082
#[test]
8183
fn test_post_commit_hook_reject_in_subfolder() {
@@ -87,11 +89,7 @@ mod tests {
8789
exit 1
8890
";
8991

90-
git2_hooks::create_hook(
91-
&repo,
92-
git2_hooks::HOOK_POST_COMMIT,
93-
hook,
94-
);
92+
create_hook(&repo, git2_hooks::HOOK_POST_COMMIT, hook);
9593

9694
let subfolder = root.join("foo/");
9795
std::fs::create_dir_all(&subfolder).unwrap();
@@ -124,11 +122,7 @@ mod tests {
124122
exit 1
125123
";
126124

127-
git2_hooks::create_hook(
128-
&repo,
129-
git2_hooks::HOOK_PRE_COMMIT,
130-
hook,
131-
);
125+
create_hook(&repo, git2_hooks::HOOK_PRE_COMMIT, hook);
132126
let res = hooks_pre_commit(repo_path).unwrap();
133127
if let HookResult::NotOk(res) = res {
134128
assert_eq!(
@@ -151,11 +145,7 @@ mod tests {
151145
exit 1
152146
";
153147

154-
git2_hooks::create_hook(
155-
&repo,
156-
git2_hooks::HOOK_COMMIT_MSG,
157-
hook,
158-
);
148+
create_hook(&repo, git2_hooks::HOOK_COMMIT_MSG, hook);
159149

160150
let subfolder = root.join("foo/");
161151
std::fs::create_dir_all(&subfolder).unwrap();
@@ -174,4 +164,37 @@ mod tests {
174164

175165
assert_eq!(msg, String::from("msg\n"));
176166
}
167+
168+
#[test]
169+
fn test_hooks_commit_msg_reject_in_hooks_folder_githooks_moved_absolute(
170+
) {
171+
let (_td, repo) = repo_init().unwrap();
172+
let root = repo.path().parent().unwrap();
173+
let mut config = repo.config().unwrap();
174+
175+
const HOOKS_DIR: &'static str = "my_hooks";
176+
config.set_str("core.hooksPath", HOOKS_DIR).unwrap();
177+
178+
let hook = b"#!/bin/sh
179+
echo 'msg' > $1
180+
echo 'rejected'
181+
exit 1
182+
";
183+
let hooks_folder = root.join(HOOKS_DIR);
184+
std::fs::create_dir_all(&hooks_folder).unwrap();
185+
create_hook_in_path(&hooks_folder.join("commit-msg"), hook);
186+
187+
let mut msg = String::from("test");
188+
let res = hooks_commit_msg(
189+
&hooks_folder.to_str().unwrap().into(),
190+
&mut msg,
191+
)
192+
.unwrap();
193+
assert_eq!(
194+
res,
195+
HookResult::NotOk(String::from("rejected\n"))
196+
);
197+
198+
assert_eq!(msg, String::from("msg\n"));
199+
}
177200
}

git2-hooks/src/hookspath.rs

+69-10
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,8 @@ impl HookPaths {
4141
if let Some(config_path) = Self::config_hook_path(repo)? {
4242
let hooks_path = PathBuf::from(config_path);
4343

44-
let hook = hooks_path.join(hook);
45-
46-
let hook = shellexpand::full(
47-
hook.as_os_str()
48-
.to_str()
49-
.ok_or(HooksError::PathToString)?,
50-
)?;
51-
52-
let hook = PathBuf::from_str(hook.as_ref())
53-
.map_err(|_| HooksError::PathToString)?;
44+
let hook =
45+
Self::expand_path(&hooks_path.join(hook), &pwd)?;
5446

5547
return Ok(Self {
5648
git: git_dir,
@@ -66,6 +58,41 @@ impl HookPaths {
6658
})
6759
}
6860

61+
/// Expand path according to the rule of githooks and config
62+
/// core.hooksPath
63+
fn expand_path(path: &Path, pwd: &Path) -> Result<PathBuf> {
64+
let hook_expanded = shellexpand::full(
65+
path.as_os_str()
66+
.to_str()
67+
.ok_or(HooksError::PathToString)?,
68+
)?;
69+
let hook_expanded = PathBuf::from_str(hook_expanded.as_ref())
70+
.map_err(|_| HooksError::PathToString)?;
71+
72+
// `man git-config`:
73+
//
74+
// > A relative path is taken as relative to the
75+
// > directory where the hooks are run (see the
76+
// > "DESCRIPTION" section of githooks[5]).
77+
//
78+
// `man githooks`:
79+
//
80+
// > Before Git invokes a hook, it changes its
81+
// > working directory to either $GIT_DIR in a bare
82+
// > repository or the root of the working tree in a
83+
// > non-bare repository.
84+
//
85+
// I.e. relative paths in core.hooksPath in non-bare
86+
// repositories are always relative to GIT_WORK_TREE.
87+
Ok({
88+
if hook_expanded.is_absolute() {
89+
hook_expanded
90+
} else {
91+
pwd.join(hook_expanded)
92+
}
93+
})
94+
}
95+
6996
fn config_hook_path(repo: &Repository) -> Result<Option<String>> {
7097
Ok(repo.config()?.get_string(CONFIG_HOOKS_PATH).ok())
7198
}
@@ -232,3 +259,35 @@ impl CommandExt for Command {
232259
self
233260
}
234261
}
262+
263+
#[cfg(test)]
264+
mod test {
265+
use super::HookPaths;
266+
use std::path::Path;
267+
268+
#[test]
269+
fn test_hookspath_relative() {
270+
assert_eq!(
271+
HookPaths::expand_path(
272+
&Path::new("pre-commit"),
273+
&Path::new("example_git_root"),
274+
)
275+
.unwrap(),
276+
Path::new("example_git_root").join("pre-commit")
277+
);
278+
}
279+
280+
#[test]
281+
fn test_hookspath_absolute() {
282+
let absolute_hook =
283+
std::env::current_dir().unwrap().join("pre-commit");
284+
assert_eq!(
285+
HookPaths::expand_path(
286+
&absolute_hook,
287+
&Path::new("example_git_root"),
288+
)
289+
.unwrap(),
290+
absolute_hook
291+
);
292+
}
293+
}

git2-hooks/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -231,13 +231,13 @@ pub mod test_utils {
231231

232232
#[cfg(test)]
233233
mod tests {
234+
use super::test_utils_priv::*;
234235
use super::*;
235236
use git2_testing::{
236237
create_hook_in_path, repo_init, repo_init_bare,
237238
};
238239
use pretty_assertions::assert_eq;
239240
use tempfile::TempDir;
240-
use super::test_utils_priv::*;
241241

242242
#[test]
243243
fn test_smoke() {

0 commit comments

Comments
 (0)