Skip to content

Commit 7f75307

Browse files
naseschwarzNaseschwarzextrawurst
authored
Resolve core.hooksPath relative to GIT_WORK_TREE (#2571)
* 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. * Favor Repository::workdir() over path().parent() This more clearly errors in case of bare repositories instead of using the parent directory of the bare repository. --------- Co-authored-by: Naseschwarz <[email protected]> Co-authored-by: extrawurst <[email protected]>
1 parent 89f73d2 commit 7f75307

File tree

3 files changed

+123
-13
lines changed

3 files changed

+123
-13
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525
* respect `.mailmap` [[@acuteenvy](https://github.com/acuteenvy)] ([#2406](https://github.com/gitui-org/gitui/issues/2406))
2626

2727
### Fixes
28+
* resolve `core.hooksPath` relative to `GIT_WORK_TREE` [[@naseschwarz](https://github.com/naseschwarz)] ([#2571](https://github.com/gitui-org/gitui/issues/2571))
2829
* yanking commit ranges no longer generates incorrect dotted range notations, but lists each individual commit [[@naseschwarz](https://github.com/naseschwarz)] (https://github.com/gitui-org/gitui/issues/2576)
2930

3031
## [0.27.0] - 2024-01-14

asyncgit/src/sync/hooks.rs

+53-3
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,28 @@ pub fn hooks_prepare_commit_msg(
7676
mod tests {
7777
use super::*;
7878
use crate::sync::tests::repo_init;
79+
use std::fs::File;
80+
use std::io::Write;
81+
use std::path::Path;
82+
83+
fn create_hook_in_path(path: &Path, hook_script: &[u8]) {
84+
File::create(path).unwrap().write_all(hook_script).unwrap();
85+
86+
#[cfg(unix)]
87+
{
88+
std::process::Command::new("chmod")
89+
.arg("+x")
90+
.arg(path)
91+
// .current_dir(path)
92+
.output()
93+
.unwrap();
94+
}
95+
}
7996

8097
#[test]
8198
fn test_post_commit_hook_reject_in_subfolder() {
8299
let (_td, repo) = repo_init().unwrap();
83-
let root = repo.path().parent().unwrap();
100+
let root = repo.workdir().unwrap();
84101

85102
let hook = b"#!/bin/sh
86103
echo 'rejected'
@@ -113,7 +130,7 @@ mod tests {
113130
#[cfg(unix)]
114131
fn test_pre_commit_workdir() {
115132
let (_td, repo) = repo_init().unwrap();
116-
let root = repo.path().parent().unwrap();
133+
let root = repo.workdir().unwrap();
117134
let repo_path: &RepoPath =
118135
&root.as_os_str().to_str().unwrap().into();
119136
let workdir =
@@ -143,7 +160,7 @@ mod tests {
143160
#[test]
144161
fn test_hooks_commit_msg_reject_in_subfolder() {
145162
let (_td, repo) = repo_init().unwrap();
146-
let root = repo.path().parent().unwrap();
163+
let root = repo.workdir().unwrap();
147164

148165
let hook = b"#!/bin/sh
149166
echo 'msg' > $1
@@ -174,4 +191,37 @@ mod tests {
174191

175192
assert_eq!(msg, String::from("msg\n"));
176193
}
194+
195+
#[test]
196+
fn test_hooks_commit_msg_reject_in_hooks_folder_githooks_moved_absolute(
197+
) {
198+
let (_td, repo) = repo_init().unwrap();
199+
let root = repo.workdir().unwrap();
200+
let mut config = repo.config().unwrap();
201+
202+
const HOOKS_DIR: &'static str = "my_hooks";
203+
config.set_str("core.hooksPath", HOOKS_DIR).unwrap();
204+
205+
let hook = b"#!/bin/sh
206+
echo 'msg' > \"$1\"
207+
echo 'rejected'
208+
exit 1
209+
";
210+
let hooks_folder = root.join(HOOKS_DIR);
211+
std::fs::create_dir_all(&hooks_folder).unwrap();
212+
create_hook_in_path(&hooks_folder.join("commit-msg"), hook);
213+
214+
let mut msg = String::from("test");
215+
let res = hooks_commit_msg(
216+
&hooks_folder.to_str().unwrap().into(),
217+
&mut msg,
218+
)
219+
.unwrap();
220+
assert_eq!(
221+
res,
222+
HookResult::NotOk(String::from("rejected\n"))
223+
);
224+
225+
assert_eq!(msg, String::from("msg\n"));
226+
}
177227
}

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+
}

0 commit comments

Comments
 (0)