From 2d90eb5c56b0e072d6e7dfa5db14396715bf9e53 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 15 Jan 2025 18:00:00 +0000 Subject: [PATCH 01/60] use /bin/sh for git-hooks --- git2-hooks/src/hookspath.rs | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 3648676ee2..e709486e03 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -3,7 +3,6 @@ use git2::Repository; use crate::{error::Result, HookResult, HooksError}; use std::{ - env, path::{Path, PathBuf}, process::Command, str::FromStr, @@ -111,16 +110,13 @@ impl HookPaths { let arg_str = format!("{:?} {}", hook, args.join(" ")); // Use -l to avoid "command not found" on Windows. - let bash_args = + let shell_args = vec!["-l".to_string(), "-c".to_string(), arg_str]; log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); - let git_shell = find_bash_executable() - .or_else(find_default_unix_shell) - .unwrap_or_else(|| "bash".into()); - let output = Command::new(git_shell) - .args(bash_args) + let output = Command::new(shell()) + .args(shell_args) .with_no_window() .current_dir(&self.pwd) // This call forces Command to handle the Path environment correctly on windows, @@ -168,15 +164,20 @@ fn is_executable(path: &Path) -> bool { } #[cfg(windows)] -/// windows does not consider bash scripts to be executable so we consider everything +/// windows does not consider shell scripts to be executable so we consider everything /// to be executable (which is not far from the truth for windows platform.) const fn is_executable(_: &Path) -> bool { true } -// Find bash.exe, and avoid finding wsl's bash.exe on Windows. -// None for non-Windows. -fn find_bash_executable() -> Option { +/// Return the shell that Git would use, the shell to execute commands from. +/// +/// On Windows, this is the full path to `sh.exe` bundled with it, and on +/// Unix it's `/bin/sh` as posix compatible shell. +/// If the bundled shell on Windows cannot be found, `sh` is returned as the name of a shell +/// as it could possibly be found in `PATH`. +/// Note that the returned path might not be a path on disk. +pub fn shell() -> PathBuf { if cfg!(windows) { Command::new("where.exe") .arg("git") @@ -190,18 +191,14 @@ fn find_bash_executable() -> Option { .as_deref() .and_then(Path::parent) .and_then(Path::parent) - .map(|p| p.join("usr/bin/bash.exe")) + .map(|p| p.join("usr/bin/sh.exe")) .filter(|p| p.exists()) + .unwrap_or_else(|| "sh".into()) } else { - None + "/bin/sh".into() } } -// Find default shell on Unix-like OS. -fn find_default_unix_shell() -> Option { - env::var_os("SHELL").map(PathBuf::from) -} - trait CommandExt { /// The process is a console application that is being run without a /// console window. Therefore, the console handle for the application is From 6a4b9d6db35745d80b8844aa0f796294ceba85fd Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 15 Jan 2025 19:00:00 +0000 Subject: [PATCH 02/60] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05823362cf..f52cd1c76d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +* use /bin/sh for git-hooks [[@Joshix](https://github.com/Joshix-1)] ([#2483](https://github.com/extrawurst/gitui/pull/2483)) ## [0.27.0] - 2024-01-14 From 8571406ff609843ee4c9761da9686bd7e6063ddb Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 15 Jan 2025 20:00:00 +0000 Subject: [PATCH 03/60] fix escape bugs in hooks --- asyncgit/src/sync/hooks.rs | 13 ++++++++++--- asyncgit/src/sync/mod.rs | 10 +++++++++- git2-hooks/src/hookspath.rs | 24 ++++++++++++++++++------ git2-hooks/src/lib.rs | 10 +++++----- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 3e82cf6f8d..3c01ee91b2 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -74,8 +74,15 @@ pub fn hooks_prepare_commit_msg( #[cfg(test)] mod tests { + use git2::Repository; + use tempfile::TempDir; + use super::*; - use crate::sync::tests::repo_init; + use crate::sync::tests::repo_init_with_prefix; + + fn repo_init() -> Result<(TempDir, Repository)> { + repo_init_with_prefix("gitui $# ") + } #[test] fn test_post_commit_hook_reject_in_subfolder() { @@ -120,7 +127,7 @@ mod tests { crate::sync::utils::repo_work_dir(repo_path).unwrap(); let hook = b"#!/bin/sh - echo $(pwd) + echo \"$(pwd)\" exit 1 "; @@ -146,7 +153,7 @@ mod tests { let root = repo.path().parent().unwrap(); let hook = b"#!/bin/sh - echo 'msg' > $1 + echo 'msg' > \"$1\" echo 'rejected' exit 1 "; diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 3c4b106c3e..feb69fba10 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -144,11 +144,19 @@ pub mod tests { /// pub fn repo_init() -> Result<(TempDir, Repository)> { + repo_init_with_prefix("gitui") + } + + /// + #[inline] + pub fn repo_init_with_prefix( + prefix: &'static str, + ) -> Result<(TempDir, Repository)> { init_log(); sandbox_config_files(); - let td = TempDir::new()?; + let td = TempDir::with_prefix(prefix)?; let repo = Repository::init(td.path())?; { let mut config = repo.config()?; diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index e709486e03..f6cde98c31 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -3,6 +3,7 @@ use git2::Repository; use crate::{error::Result, HookResult, HooksError}; use std::{ + ffi::{OsStr, OsString}, path::{Path, PathBuf}, process::Command, str::FromStr, @@ -107,12 +108,23 @@ impl HookPaths { /// see pub fn run_hook(&self, args: &[&str]) -> Result { let hook = self.hook.clone(); - - let arg_str = format!("{:?} {}", hook, args.join(" ")); - // Use -l to avoid "command not found" on Windows. - let shell_args = - vec!["-l".to_string(), "-c".to_string(), arg_str]; - + let command = { + let mut os_str = OsString::new(); + os_str.push("'"); + os_str.push(hook.as_os_str()); // TODO: this doesn't work if `hook` contains single-quotes + os_str.push("'"); + os_str.push(" \"$@\""); + os_str + }; + let shell_args = { + let mut shell_args = vec![ + OsStr::new("-c"), + command.as_os_str(), + hook.as_os_str(), + ]; + shell_args.extend(args.iter().map(OsStr::new)); + shell_args + }; log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); let output = Command::new(shell()) diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index 2a458856d7..d8d5f796e4 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -282,7 +282,7 @@ exit 0 let hook = br#"#!/bin/sh COMMIT_MSG="$(cat "$1")" -printf "$COMMIT_MSG" | sed 's/sth/shell_command/g' >"$1" +printf "$COMMIT_MSG" | sed 's/sth/shell_command/g' > "$1" exit 0 "#; @@ -499,7 +499,7 @@ sys.exit(1) let (_td, repo) = repo_init(); let hook = b"#!/bin/sh -echo 'msg' > $1 +echo 'msg' > \"$1\" echo 'rejected' exit 1 "; @@ -525,7 +525,7 @@ exit 1 let (_td, repo) = repo_init(); let hook = b"#!/bin/sh -echo 'msg' > $1 +echo 'msg' > \"$1\" exit 0 "; @@ -565,7 +565,7 @@ exit 0 let (_td, repo) = repo_init(); let hook = b"#!/bin/sh -echo msg:$2 > $1 +echo \"msg:$2\" > \"$1\" exit 0 "; @@ -589,7 +589,7 @@ exit 0 let (_td, repo) = repo_init(); let hook = b"#!/bin/sh -echo $2,$3 > $1 +echo \"$2,$3\" > \"$1\" echo 'rejected' exit 2 "; From cba51424ac0f40033bfde675a6e4b27da363f82a Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 15 Jan 2025 21:00:00 +0000 Subject: [PATCH 04/60] simplify logic for running hooks on non-windows platforms --- git2-hooks/src/hookspath.rs | 118 ++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index f6cde98c31..b1aa4469c2 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -3,7 +3,7 @@ use git2::Repository; use crate::{error::Result, HookResult, HooksError}; use std::{ - ffi::{OsStr, OsString}, + ffi::OsStr, path::{Path, PathBuf}, process::Command, str::FromStr, @@ -108,37 +108,45 @@ impl HookPaths { /// see pub fn run_hook(&self, args: &[&str]) -> Result { let hook = self.hook.clone(); - let command = { - let mut os_str = OsString::new(); - os_str.push("'"); - os_str.push(hook.as_os_str()); // TODO: this doesn't work if `hook` contains single-quotes - os_str.push("'"); - os_str.push(" \"$@\""); - os_str - }; - let shell_args = { - let mut shell_args = vec![ - OsStr::new("-c"), - command.as_os_str(), - hook.as_os_str(), - ]; - shell_args.extend(args.iter().map(OsStr::new)); - shell_args - }; log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); - let output = Command::new(shell()) - .args(shell_args) - .with_no_window() - .current_dir(&self.pwd) - // This call forces Command to handle the Path environment correctly on windows, - // the specific env set here does not matter - // see https://github.com/rust-lang/rust/issues/37519 - .env( - "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", - "FixPathHandlingOnWindows", + let run_command = |command: &mut Command| { + command.current_dir(&self.pwd).with_no_window().output() + }; + let output = if cfg!(windows) { + run_command(Command::new(&hook).args(args)) + } else { + let command = { + let mut os_str = std::ffi::OsString::new(); + os_str.push("'"); + os_str.push(hook.as_os_str()); // TODO: this doesn't work if `hook` contains single-quotes + os_str.push("'"); + os_str.push(" \"$@\""); + os_str + }; + let shell_args = { + let mut shell_args = vec![ + OsStr::new("-l"), // Use -l to avoid "command not found" + OsStr::new("-c"), + command.as_os_str(), + hook.as_os_str(), + ]; + shell_args.extend(args.iter().map(OsStr::new)); + shell_args + }; + + run_command( + Command::new(sh_on_windows()) + .args(shell_args) + // This call forces Command to handle the Path environment correctly on windows, + // the specific env set here does not matter + // see https://github.com/rust-lang/rust/issues/37519 + .env( + "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", + "FixPathHandlingOnWindows", + ), ) - .output()?; + }?; if output.status.success() { Ok(HookResult::Ok { hook }) @@ -158,6 +166,29 @@ impl HookPaths { } } +/// Get the path to the sh.exe bundled with Git for Windows +fn sh_on_windows() -> PathBuf { + if cfg!(windows) { + Command::new("where.exe") + .arg("git") + .output() + .ok() + .map(|out| { + PathBuf::from(Into::::into( + String::from_utf8_lossy(&out.stdout), + )) + }) + .as_deref() + .and_then(Path::parent) + .and_then(Path::parent) + .map(|p| p.join("usr/bin/sh.exe")) + .filter(|p| p.exists()) + .unwrap_or_else(|| "sh".into()) + } else { + "sh".into() + } +} + #[cfg(unix)] fn is_executable(path: &Path) -> bool { use std::os::unix::fs::PermissionsExt; @@ -182,35 +213,6 @@ const fn is_executable(_: &Path) -> bool { true } -/// Return the shell that Git would use, the shell to execute commands from. -/// -/// On Windows, this is the full path to `sh.exe` bundled with it, and on -/// Unix it's `/bin/sh` as posix compatible shell. -/// If the bundled shell on Windows cannot be found, `sh` is returned as the name of a shell -/// as it could possibly be found in `PATH`. -/// Note that the returned path might not be a path on disk. -pub fn shell() -> PathBuf { - if cfg!(windows) { - Command::new("where.exe") - .arg("git") - .output() - .ok() - .map(|out| { - PathBuf::from(Into::::into( - String::from_utf8_lossy(&out.stdout), - )) - }) - .as_deref() - .and_then(Path::parent) - .and_then(Path::parent) - .map(|p| p.join("usr/bin/sh.exe")) - .filter(|p| p.exists()) - .unwrap_or_else(|| "sh".into()) - } else { - "/bin/sh".into() - } -} - trait CommandExt { /// The process is a console application that is being run without a /// console window. Therefore, the console handle for the application is From 7f70d526a5aedbca51a7372e910956bdb76749e7 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 15 Jan 2025 21:00:00 +0000 Subject: [PATCH 05/60] invert if check --- git2-hooks/src/hookspath.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index b1aa4469c2..8c8cf86d17 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -114,8 +114,6 @@ impl HookPaths { command.current_dir(&self.pwd).with_no_window().output() }; let output = if cfg!(windows) { - run_command(Command::new(&hook).args(args)) - } else { let command = { let mut os_str = std::ffi::OsString::new(); os_str.push("'"); @@ -146,6 +144,8 @@ impl HookPaths { "FixPathHandlingOnWindows", ), ) + } else { + run_command(Command::new(&hook).args(args)) }?; if output.status.success() { From bd61ad8047c2e81a212010e3b0d0b642bf22c480 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 15 Jan 2025 21:00:00 +0000 Subject: [PATCH 06/60] debug_assert --- git2-hooks/src/hookspath.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 8c8cf86d17..57446eeeb0 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -185,6 +185,7 @@ fn sh_on_windows() -> PathBuf { .filter(|p| p.exists()) .unwrap_or_else(|| "sh".into()) } else { + debug_assert!(false, "should only be called on windows"); "sh".into() } } From c257d02507d120c6bc74efc03f6ec550e32f318d Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 15 Jan 2025 21:00:00 +0000 Subject: [PATCH 07/60] update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f52cd1c76d..b1d82b0b8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased -* use /bin/sh for git-hooks [[@Joshix](https://github.com/Joshix-1)] ([#2483](https://github.com/extrawurst/gitui/pull/2483)) +* execute git-hooks without shell (on *nix) [[@Joshix](https://github.com/Joshix-1)] ([#2483](https://github.com/extrawurst/gitui/pull/2483)) ## [0.27.0] - 2024-01-14 From da71107e460357c642a3bac4ce2629fb6262cc98 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 18:11:46 +0100 Subject: [PATCH 08/60] create build_shell_script_execution_command --- git2-hooks/src/hookspath.rs | 61 ++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 57446eeeb0..0c0df5289e 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -114,28 +114,8 @@ impl HookPaths { command.current_dir(&self.pwd).with_no_window().output() }; let output = if cfg!(windows) { - let command = { - let mut os_str = std::ffi::OsString::new(); - os_str.push("'"); - os_str.push(hook.as_os_str()); // TODO: this doesn't work if `hook` contains single-quotes - os_str.push("'"); - os_str.push(" \"$@\""); - os_str - }; - let shell_args = { - let mut shell_args = vec![ - OsStr::new("-l"), // Use -l to avoid "command not found" - OsStr::new("-c"), - command.as_os_str(), - hook.as_os_str(), - ]; - shell_args.extend(args.iter().map(OsStr::new)); - shell_args - }; - run_command( - Command::new(sh_on_windows()) - .args(shell_args) + build_shell_script_execution_command(&hook, args) // This call forces Command to handle the Path environment correctly on windows, // the specific env set here does not matter // see https://github.com/rust-lang/rust/issues/37519 @@ -145,7 +125,10 @@ impl HookPaths { ), ) } else { - run_command(Command::new(&hook).args(args)) + run_command(&mut build_shell_script_execution_command( + &hook, args, + )) + //run_command(Command::new(&hook).args(args)) }?; if output.status.success() { @@ -166,8 +149,37 @@ impl HookPaths { } } -/// Get the path to the sh.exe bundled with Git for Windows -fn sh_on_windows() -> PathBuf { +fn build_shell_script_execution_command( + script: &Path, + args: &[&str], +) -> Command { + let command = { + let mut os_str = std::ffi::OsString::new(); + os_str.push("'"); + os_str.push(script.as_os_str()); // TODO: this doesn't work if `script` contains single-quotes + os_str.push("'"); + os_str.push(" \"$@\""); + os_str + }; + let shell_args = { + let mut shell_args = vec![ + OsStr::new("-l"), // Use -l to avoid "command not found" + OsStr::new("-c"), + command.as_os_str(), + script.as_os_str(), + ]; + shell_args.extend(args.iter().map(OsStr::new)); + shell_args + }; + + let mut command = Command::new(sh_path()); + command.args(shell_args); + command +} + +/// Get the path to the sh executable. +/// On Windows get the sh.exe bundled with Git for Windows +pub fn sh_path() -> PathBuf { if cfg!(windows) { Command::new("where.exe") .arg("git") @@ -185,7 +197,6 @@ fn sh_on_windows() -> PathBuf { .filter(|p| p.exists()) .unwrap_or_else(|| "sh".into()) } else { - debug_assert!(false, "should only be called on windows"); "sh".into() } } From 2b6d2955d3bfd3f76511dd6336a8d1ec85af967f Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 17:00:00 +0000 Subject: [PATCH 09/60] small refactorings --- git2-hooks/src/hookspath.rs | 63 +++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 0c0df5289e..3130f81ed8 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -110,26 +110,21 @@ impl HookPaths { let hook = self.hook.clone(); log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); - let run_command = |command: &mut Command| { - command.current_dir(&self.pwd).with_no_window().output() - }; - let output = if cfg!(windows) { - run_command( - build_shell_script_execution_command(&hook, args) - // This call forces Command to handle the Path environment correctly on windows, - // the specific env set here does not matter - // see https://github.com/rust-lang/rust/issues/37519 - .env( - "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", - "FixPathHandlingOnWindows", - ), - ) + let mut command = if cfg!(windows) { + build_shell_script_execution_command(&hook, args) } else { - run_command(&mut build_shell_script_execution_command( - &hook, args, - )) + eprintln!("linux"); + + let c = build_shell_script_execution_command(&hook, args); //run_command(Command::new(&hook).args(args)) - }?; + eprintln!("{:?}", c.get_args()); + c + }; + + let output = command + .current_dir(&self.pwd) + .with_no_window() + .output()?; if output.status.success() { Ok(HookResult::Ok { hook }) @@ -153,7 +148,7 @@ fn build_shell_script_execution_command( script: &Path, args: &[&str], ) -> Command { - let command = { + let command_string = { let mut os_str = std::ffi::OsString::new(); os_str.push("'"); os_str.push(script.as_os_str()); // TODO: this doesn't work if `script` contains single-quotes @@ -161,19 +156,27 @@ fn build_shell_script_execution_command( os_str.push(" \"$@\""); os_str }; - let shell_args = { - let mut shell_args = vec![ - OsStr::new("-l"), // Use -l to avoid "command not found" - OsStr::new("-c"), - command.as_os_str(), - script.as_os_str(), - ]; - shell_args.extend(args.iter().map(OsStr::new)); - shell_args - }; let mut command = Command::new(sh_path()); - command.args(shell_args); + + if cfg!(windows) { + // Use -l to avoid "command not found" + command.arg(OsStr::new("-l")); + } + command.args([OsStr::new("-c"), command_string.as_os_str()]); + + command.arg(script.as_os_str()); + command.args(args.iter().map(OsStr::new)); + + if cfg!(windows) { + // This call forces Command to handle the Path environment correctly on windows, + // the specific env set here does not matter + // see https://github.com/rust-lang/rust/issues/37519 + command.env( + "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", + "FixPathHandlingOnWindows", + ); + } command } From 816e17528d39e5fa3451ce319e1b0b39b9792a85 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 17:00:00 +0000 Subject: [PATCH 10/60] simplify build_shell_script_execution_command --- git2-hooks/src/hookspath.rs | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 3130f81ed8..2b4a7c40d4 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -3,7 +3,6 @@ use git2::Repository; use crate::{error::Result, HookResult, HooksError}; use std::{ - ffi::OsStr, path::{Path, PathBuf}, process::Command, str::FromStr, @@ -148,26 +147,8 @@ fn build_shell_script_execution_command( script: &Path, args: &[&str], ) -> Command { - let command_string = { - let mut os_str = std::ffi::OsString::new(); - os_str.push("'"); - os_str.push(script.as_os_str()); // TODO: this doesn't work if `script` contains single-quotes - os_str.push("'"); - os_str.push(" \"$@\""); - os_str - }; - let mut command = Command::new(sh_path()); - if cfg!(windows) { - // Use -l to avoid "command not found" - command.arg(OsStr::new("-l")); - } - command.args([OsStr::new("-c"), command_string.as_os_str()]); - - command.arg(script.as_os_str()); - command.args(args.iter().map(OsStr::new)); - if cfg!(windows) { // This call forces Command to handle the Path environment correctly on windows, // the specific env set here does not matter @@ -176,7 +157,14 @@ fn build_shell_script_execution_command( "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", "FixPathHandlingOnWindows", ); + // Use -l to avoid "command not found" + command.arg("-l"); } + command + .arg(script) // the script to execute + .arg(script) // argv[0] + .args(args); // argv[1..] + command } From ce9fc0ea7561c2b9065e951974facbe05514f1cf Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 19:00:20 +0100 Subject: [PATCH 11/60] add line --- git2-hooks/src/hookspath.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 2b4a7c40d4..f61098f5c0 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -157,6 +157,7 @@ fn build_shell_script_execution_command( "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", "FixPathHandlingOnWindows", ); + // Use -l to avoid "command not found" command.arg("-l"); } From 04a31eb8519c7d72911068aed530a7842ae1f884 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 19:16:28 +0100 Subject: [PATCH 12/60] execute hoooks directly and fallback to using sh --- git2-hooks/src/hookspath.rs | 38 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index f61098f5c0..96c7581343 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -109,21 +109,27 @@ impl HookPaths { let hook = self.hook.clone(); log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); - let mut command = if cfg!(windows) { - build_shell_script_execution_command(&hook, args) - } else { - eprintln!("linux"); - - let c = build_shell_script_execution_command(&hook, args); - //run_command(Command::new(&hook).args(args)) - eprintln!("{:?}", c.get_args()); - c + let run_command = |mut command: Command| { + command.current_dir(&self.pwd).with_no_window().output() }; - let output = command - .current_dir(&self.pwd) - .with_no_window() - .output()?; + let output = if cfg!(windows) { + let command = + build_shell_script_execution_command(&hook, args); + run_command(command) + } else { + let mut command = Command::new(&hook); + command.args(args); + match run_command(command) { + Err(err) if err.raw_os_error() == Some(8) => { + run_command(build_shell_script_execution_command( + &hook, args, + )) + } + + result => result, + } + }?; if output.status.success() { Ok(HookResult::Ok { hook }) @@ -161,10 +167,8 @@ fn build_shell_script_execution_command( // Use -l to avoid "command not found" command.arg("-l"); } - command - .arg(script) // the script to execute - .arg(script) // argv[0] - .args(args); // argv[1..] + + command.arg(script).args(args); command } From 8ba997d34e59fcc88d0cd6f5c7d22eb9af269cf9 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 18:00:00 +0000 Subject: [PATCH 13/60] rename function --- git2-hooks/src/hookspath.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 96c7581343..ad32435da6 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -114,17 +114,14 @@ impl HookPaths { }; let output = if cfg!(windows) { - let command = - build_shell_script_execution_command(&hook, args); - run_command(command) + run_command(sh_command(&hook, args)) } else { let mut command = Command::new(&hook); command.args(args); match run_command(command) { Err(err) if err.raw_os_error() == Some(8) => { - run_command(build_shell_script_execution_command( - &hook, args, - )) + // if execution failed with ENOEXEC execute with /bin/sh + run_command(sh_command(&hook, args)) } result => result, @@ -149,10 +146,7 @@ impl HookPaths { } } -fn build_shell_script_execution_command( - script: &Path, - args: &[&str], -) -> Command { +fn sh_command(script: &Path, args: &[&str]) -> Command { let mut command = Command::new(sh_path()); if cfg!(windows) { From 4f65b8148926b0a5793f2ca446be608bfd2e953c Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 19:20:23 +0100 Subject: [PATCH 14/60] update commend --- git2-hooks/src/hookspath.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index ad32435da6..606768ae8e 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -120,7 +120,7 @@ impl HookPaths { command.args(args); match run_command(command) { Err(err) if err.raw_os_error() == Some(8) => { - // if execution failed with ENOEXEC execute with /bin/sh + // if error is ENOEXEC execute with sh run_command(sh_command(&hook, args)) } From 58afb49eed0ceae7bf7a910e81d368f2c4352531 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 18:00:00 +0000 Subject: [PATCH 15/60] sh_command --- git2-hooks/src/hookspath.rs | 46 ++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 606768ae8e..45625d1590 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -109,24 +109,15 @@ impl HookPaths { let hook = self.hook.clone(); log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); - let run_command = |mut command: Command| { - command.current_dir(&self.pwd).with_no_window().output() - }; - - let output = if cfg!(windows) { - run_command(sh_command(&hook, args)) + let mut command = if cfg!(windows) { + sh_command(&hook, args) } else { let mut command = Command::new(&hook); command.args(args); - match run_command(command) { - Err(err) if err.raw_os_error() == Some(8) => { - // if error is ENOEXEC execute with sh - run_command(sh_command(&hook, args)) - } + command + }; - result => result, - } - }?; + let output = command.current_dir(&self.pwd).output()?; if output.status.success() { Ok(HookResult::Ok { hook }) @@ -160,6 +151,8 @@ fn sh_command(script: &Path, args: &[&str]) -> Command { // Use -l to avoid "command not found" command.arg("-l"); + + command.with_no_window(); } command.arg(script).args(args); @@ -192,20 +185,21 @@ pub fn sh_path() -> PathBuf { } #[cfg(unix)] -fn is_executable(path: &Path) -> bool { - use std::os::unix::fs::PermissionsExt; - - let metadata = match path.metadata() { - Ok(metadata) => metadata, - Err(e) => { - log::error!("metadata error: {}", e); - return false; - } - }; +const fn is_executable(path: &Path) -> bool { + // use std::os::unix::fs::PermissionsExt; - let permissions = metadata.permissions(); + // let metadata = match path.metadata() { + // Ok(metadata) => metadata, + // Err(e) => { + // log::error!("metadata error: {}", e); + // return false; + // } + // }; - permissions.mode() & 0o111 != 0 + // let permissions = metadata.permissions(); + + // permissions.mode() & 0o111 != 0 + true } #[cfg(windows)] From a6e71d3129357ce144febbf2c9b9084f45027c8a Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 19:58:30 +0100 Subject: [PATCH 16/60] refactor --- git2-hooks/src/hookspath.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 45625d1590..72c26e545b 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -110,14 +110,18 @@ impl HookPaths { log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); let mut command = if cfg!(windows) { - sh_command(&hook, args) + // execute hook with sh + sh_command(&hook) } else { - let mut command = Command::new(&hook); - command.args(args); - command + // execute hook directly + Command::new(&hook) }; - let output = command.current_dir(&self.pwd).output()?; + let output = command + .args(args) + .current_dir(&self.pwd) + .with_no_window() + .output()?; if output.status.success() { Ok(HookResult::Ok { hook }) @@ -137,7 +141,7 @@ impl HookPaths { } } -fn sh_command(script: &Path, args: &[&str]) -> Command { +fn sh_command(script: &Path) -> Command { let mut command = Command::new(sh_path()); if cfg!(windows) { @@ -151,11 +155,9 @@ fn sh_command(script: &Path, args: &[&str]) -> Command { // Use -l to avoid "command not found" command.arg("-l"); - - command.with_no_window(); } - command.arg(script).args(args); + command.arg(script); command } From dbd8feecdea8a139c5e03e919b01fe675d54e13c Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:02:12 +0100 Subject: [PATCH 17/60] fix is_executable --- git2-hooks/src/hookspath.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 72c26e545b..e013a74546 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -187,21 +187,20 @@ pub fn sh_path() -> PathBuf { } #[cfg(unix)] -const fn is_executable(path: &Path) -> bool { - // use std::os::unix::fs::PermissionsExt; - - // let metadata = match path.metadata() { - // Ok(metadata) => metadata, - // Err(e) => { - // log::error!("metadata error: {}", e); - // return false; - // } - // }; +fn is_executable(path: &Path) -> bool { + use std::os::unix::fs::PermissionsExt; + + let metadata = match path.metadata() { + Ok(metadata) => metadata, + Err(e) => { + log::error!("metadata error: {}", e); + return false; + } + }; - // let permissions = metadata.permissions(); + let permissions = metadata.permissions(); - // permissions.mode() & 0o111 != 0 - true + permissions.mode() & 0o111 != 0 } #[cfg(windows)] From 7c8b6bc277116d98e1445d9cb5a8571f90c309fd Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:03:27 +0100 Subject: [PATCH 18/60] debug print --- git2-hooks/src/hookspath.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index e013a74546..d8f4769079 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -124,6 +124,7 @@ impl HookPaths { .output()?; if output.status.success() { + eprintln!("{}", String::from_utf8_lossy(&output.stdout)); Ok(HookResult::Ok { hook }) } else { let stderr = From 1370a14afb03a2e6c61e7353440eba62bbe3cdef Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:07:44 +0100 Subject: [PATCH 19/60] handle ENOEXEC --- git2-hooks/src/hookspath.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index d8f4769079..dd2f3c098a 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -109,22 +109,28 @@ impl HookPaths { let hook = self.hook.clone(); log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); - let mut command = if cfg!(windows) { + let run_command = |mut command: Command| { + command + .args(args) + .current_dir(&self.pwd) + .with_no_window() + .output() + }; + + let output = if cfg!(windows) { // execute hook with sh - sh_command(&hook) + run_command(sh_command(&hook)) } else { // execute hook directly - Command::new(&hook) - }; - - let output = command - .args(args) - .current_dir(&self.pwd) - .with_no_window() - .output()?; + match run_command(Command::new(&hook)) { + Err(err) if err.raw_os_error() == Some(8) => { + run_command(sh_command(&hook)) + } + result => result, + } + }?; if output.status.success() { - eprintln!("{}", String::from_utf8_lossy(&output.stdout)); Ok(HookResult::Ok { hook }) } else { let stderr = From 65c59970949aa15aa14ee07351adca281fb3575a Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:08:29 +0100 Subject: [PATCH 20/60] add debug print --- git2-hooks/src/hookspath.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index dd2f3c098a..1f5e5bd2ee 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -124,6 +124,7 @@ impl HookPaths { // execute hook directly match run_command(Command::new(&hook)) { Err(err) if err.raw_os_error() == Some(8) => { + eprintln!("GOT ENOEXEC: {err}"); run_command(sh_command(&hook)) } result => result, From 102e7fdb05935d6c3be93baa55028a66fa576f84 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 19:00:00 +0000 Subject: [PATCH 21/60] remove debug print --- git2-hooks/src/hookspath.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 1f5e5bd2ee..dd2f3c098a 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -124,7 +124,6 @@ impl HookPaths { // execute hook directly match run_command(Command::new(&hook)) { Err(err) if err.raw_os_error() == Some(8) => { - eprintln!("GOT ENOEXEC: {err}"); run_command(sh_command(&hook)) } result => result, From b79e5ce2c7bd12f972cd06037e307c7910b16796 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 19:00:00 +0000 Subject: [PATCH 22/60] name const --- git2-hooks/src/hookspath.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index dd2f3c098a..f46abac734 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -16,6 +16,7 @@ pub struct HookPaths { const CONFIG_HOOKS_PATH: &str = "core.hooksPath"; const DEFAULT_HOOKS_PATH: &str = "hooks"; +const ENOEXEC: i32 = 8; impl HookPaths { /// `core.hooksPath` always takes precedence. @@ -123,7 +124,7 @@ impl HookPaths { } else { // execute hook directly match run_command(Command::new(&hook)) { - Err(err) if err.raw_os_error() == Some(8) => { + Err(err) if err.raw_os_error() == Some(ENOEXEC) => { run_command(sh_command(&hook)) } result => result, From 177d9d8aead3d0834f9b48bcfc81bb5802e2ffc9 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 19:00:00 +0000 Subject: [PATCH 23/60] update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 375b73d51d..caf473ce95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased -* execute git-hooks without shell (on *nix) [[@Joshix](https://github.com/Joshix-1)] ([#2483](https://github.com/extrawurst/gitui/pull/2483)) +* execute git-hooks directly if possible (on *nix) else use sh instead of bash (without reading SHELL variable) [[@Joshix](https://github.com/Joshix-1)] ([#2483](https://github.com/extrawurst/gitui/pull/2483)) ### Added * support loading custom syntax highlighting themes from a file [[@acuteenvy](https://github.com/acuteenvy)] ([#2565](https://github.com/gitui-org/gitui/pull/2565)) From e5e562ef898ae1ae843ebbe04045153dc6778446 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 19:00:00 +0000 Subject: [PATCH 24/60] add new test --- git2-hooks/src/lib.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index d8d5f796e4..e942fea531 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -309,6 +309,39 @@ exit 0 assert!(res.is_ok()); } + #[test] + fn test_hook_with_missing_shebang() { + const TEXT: &str = "Hello, world!"; + + let (_td, repo) = repo_init(); + + let hook = b"echo \"$@\"\nexit 42"; + + create_hook(&repo, HOOK_PRE_COMMIT, hook); + + let hook = + HookPaths::new(&repo, None, HOOK_PRE_COMMIT).unwrap(); + + assert!(hook.found()); + + let result = hook.run_hook(&[TEXT]).unwrap(); + + if let HookResult::RunNotSuccessful { + code, + stdout, + stderr, + hook: h, + } = result + { + assert_eq!(code, Some(42)); + assert_eq!(stdout.as_str().trim_end(), TEXT); + assert!(stderr.is_empty()); + assert_eq!(h, hook.hook); + } else { + panic!("run_hook should've failed"); + } + } + #[test] fn test_no_hook_found() { let (_td, repo) = repo_init(); From f397799f8f40151e3a40ad8ada974c1a225fe8da Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 19:00:00 +0000 Subject: [PATCH 25/60] deindent --- asyncgit/src/sync/hooks.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 3c01ee91b2..396908bcfc 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -90,9 +90,9 @@ mod tests { let root = repo.path().parent().unwrap(); let hook = b"#!/bin/sh - echo 'rejected' - exit 1 - "; +echo 'rejected' +exit 1 +"; git2_hooks::create_hook( &repo, @@ -127,10 +127,9 @@ mod tests { crate::sync::utils::repo_work_dir(repo_path).unwrap(); let hook = b"#!/bin/sh - echo \"$(pwd)\" - exit 1 - "; - +echo \"$(pwd)\" +exit 1 +"; git2_hooks::create_hook( &repo, git2_hooks::HOOK_PRE_COMMIT, @@ -153,10 +152,9 @@ mod tests { let root = repo.path().parent().unwrap(); let hook = b"#!/bin/sh - echo 'msg' > \"$1\" - echo 'rejected' - exit 1 - "; +echo 'msg' > \"$1\" +echo 'rejected' +exit 1"; git2_hooks::create_hook( &repo, From 26b88714992f62d2f7124c11d63836357c204025 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:00:00 +0000 Subject: [PATCH 26/60] set different env var https://superuser.com/questions/1641219/git-bash-with-windows-terminal-prints-weird-characters-instead-of-newline --- git2-hooks/src/hookspath.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index f46abac734..9ca27d2a35 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -156,10 +156,7 @@ fn sh_command(script: &Path) -> Command { // This call forces Command to handle the Path environment correctly on windows, // the specific env set here does not matter // see https://github.com/rust-lang/rust/issues/37519 - command.env( - "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", - "FixPathHandlingOnWindows", - ); + command.env("TERM", "cygwin"); // Use -l to avoid "command not found" command.arg("-l"); From 5327f4a4a1e51271582f59a4db715a46a55bda3d Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:00:00 +0000 Subject: [PATCH 27/60] Revert "set different env var" This reverts commit 26b88714992f62d2f7124c11d63836357c204025. --- git2-hooks/src/hookspath.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 9ca27d2a35..f46abac734 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -156,7 +156,10 @@ fn sh_command(script: &Path) -> Command { // This call forces Command to handle the Path environment correctly on windows, // the specific env set here does not matter // see https://github.com/rust-lang/rust/issues/37519 - command.env("TERM", "cygwin"); + command.env( + "DUMMY_ENV_TO_FIX_WINDOWS_CMD_RUNS", + "FixPathHandlingOnWindows", + ); // Use -l to avoid "command not found" command.arg("-l"); From 11da04a7b9dc2045c9128541e326fefa185de690 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:00:00 +0000 Subject: [PATCH 28/60] remove ansi clear from output --- git2-hooks/src/hookspath.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index f46abac734..ed2b0d8c80 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -134,11 +134,22 @@ impl HookPaths { if output.status.success() { Ok(HookResult::Ok { hook }) } else { - let stderr = + let mut stderr = String::from_utf8_lossy(&output.stderr).to_string(); - let stdout = + let mut stdout = String::from_utf8_lossy(&output.stdout).to_string(); + if cfg!(windows) { + const ANSI_CLEAR: &str = "\x1B[H\x1B[J"; + + if let Some(value) = stdout.strip_suffix(ANSI_CLEAR) { + stderr.truncate(value.len()); + } + if let Some(value) = stdout.strip_suffix(ANSI_CLEAR) { + stdout.truncate(value.len()); + } + } + Ok(HookResult::RunNotSuccessful { code: output.status.code(), stdout, From cfdb19609397105e67dbfeb5b6040d7aa9759476 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:00:00 +0000 Subject: [PATCH 29/60] fix typo --- git2-hooks/src/hookspath.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index ed2b0d8c80..84fe6fbf5c 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -142,7 +142,7 @@ impl HookPaths { if cfg!(windows) { const ANSI_CLEAR: &str = "\x1B[H\x1B[J"; - if let Some(value) = stdout.strip_suffix(ANSI_CLEAR) { + if let Some(value) = stderr.strip_suffix(ANSI_CLEAR) { stderr.truncate(value.len()); } if let Some(value) = stdout.strip_suffix(ANSI_CLEAR) { From 08dce84fa351aafe4be05bdeb81a1cb91d193964 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:00:00 +0000 Subject: [PATCH 30/60] move ansi escape hack to impl From for HookResult --- asyncgit/src/sync/hooks.rs | 12 ++++++++++-- git2-hooks/src/hookspath.rs | 15 ++------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 396908bcfc..a3818c8be4 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -19,9 +19,17 @@ impl From for HookResult { | git2_hooks::HookResult::NoHookFound => Self::Ok, git2_hooks::HookResult::RunNotSuccessful { stdout, - stderr, + mut stderr, .. - } => Self::NotOk(format!("{stdout}{stderr}")), + } => { + const ANSI_CLEAR: &str = "\x1B[H\x1B[J"; + + if stderr == ANSI_CLEAR { + stderr.clear(); + } + + Self::NotOk(format!("{stdout}{stderr}")) + } } } } diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 84fe6fbf5c..f46abac734 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -134,22 +134,11 @@ impl HookPaths { if output.status.success() { Ok(HookResult::Ok { hook }) } else { - let mut stderr = + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); - let mut stdout = + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); - if cfg!(windows) { - const ANSI_CLEAR: &str = "\x1B[H\x1B[J"; - - if let Some(value) = stderr.strip_suffix(ANSI_CLEAR) { - stderr.truncate(value.len()); - } - if let Some(value) = stdout.strip_suffix(ANSI_CLEAR) { - stdout.truncate(value.len()); - } - } - Ok(HookResult::RunNotSuccessful { code: output.status.code(), stdout, From b37817db75f133127a8b99f40b493591bb544401 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:00:00 +0000 Subject: [PATCH 31/60] fix ansi clear --- asyncgit/src/sync/hooks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index a3818c8be4..4889ca35e3 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -22,7 +22,7 @@ impl From for HookResult { mut stderr, .. } => { - const ANSI_CLEAR: &str = "\x1B[H\x1B[J"; + const ANSI_CLEAR: &str = "\x1B[H\x1B[2J\x1B[3J"; if stderr == ANSI_CLEAR { stderr.clear(); From a7fe9cb9869baf51e9cbaafee9e5eefcbd27be8d Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:00:00 +0000 Subject: [PATCH 32/60] try strip different --- asyncgit/src/sync/hooks.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 4889ca35e3..4046ffd947 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -19,16 +19,20 @@ impl From for HookResult { | git2_hooks::HookResult::NoHookFound => Self::Ok, git2_hooks::HookResult::RunNotSuccessful { stdout, - mut stderr, + stderr, .. } => { const ANSI_CLEAR: &str = "\x1B[H\x1B[2J\x1B[3J"; - if stderr == ANSI_CLEAR { - stderr.clear(); + let mut combined = format!("{stdout}{stderr}"); + + if let Some(trimmed) = + combined.strip_suffix(ANSI_CLEAR) + { + combined.truncate(trimmed.len()); } - Self::NotOk(format!("{stdout}{stderr}")) + Self::NotOk(combined) } } } From a4e2f54ec9540b7aa717fcb90410a8144f224220 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:00:00 +0000 Subject: [PATCH 33/60] more trimming --- asyncgit/src/sync/hooks.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 4046ffd947..ae32b1b4aa 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -24,7 +24,15 @@ impl From for HookResult { } => { const ANSI_CLEAR: &str = "\x1B[H\x1B[2J\x1B[3J"; - let mut combined = format!("{stdout}{stderr}"); + let mut combined = stdout; + + if let Some(trimmed) = + combined.strip_suffix(ANSI_CLEAR) + { + combined.truncate(trimmed.len()); + } + + combined.push_str(&stderr); if let Some(trimmed) = combined.strip_suffix(ANSI_CLEAR) From d25efdc2d25d08beb86b4e820362375a560da231 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 20:00:00 +0000 Subject: [PATCH 34/60] Revert "more trimming" This reverts commit a4e2f54ec9540b7aa717fcb90410a8144f224220. --- asyncgit/src/sync/hooks.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index ae32b1b4aa..4046ffd947 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -24,15 +24,7 @@ impl From for HookResult { } => { const ANSI_CLEAR: &str = "\x1B[H\x1B[2J\x1B[3J"; - let mut combined = stdout; - - if let Some(trimmed) = - combined.strip_suffix(ANSI_CLEAR) - { - combined.truncate(trimmed.len()); - } - - combined.push_str(&stderr); + let mut combined = format!("{stdout}{stderr}"); if let Some(trimmed) = combined.strip_suffix(ANSI_CLEAR) From caf83c8008994fb1156cadad2bd8b7ffc19bee42 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 21:00:00 +0000 Subject: [PATCH 35/60] weird hack only on windows --- asyncgit/src/sync/hooks.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 4046ffd947..6f0b4dbec2 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -22,14 +22,16 @@ impl From for HookResult { stderr, .. } => { - const ANSI_CLEAR: &str = "\x1B[H\x1B[2J\x1B[3J"; - let mut combined = format!("{stdout}{stderr}"); - if let Some(trimmed) = - combined.strip_suffix(ANSI_CLEAR) - { - combined.truncate(trimmed.len()); + if cfg!(windows) { + const ANSI_CLEAR: &str = "\x1B[H\x1B[2J\x1B[3J"; + + if let Some(trimmed) = + combined.strip_suffix(ANSI_CLEAR) + { + combined.truncate(trimmed.len()); + } } Self::NotOk(combined) From f91aa105a667640eae4c79b0b298be48d4672344 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 21:00:00 +0000 Subject: [PATCH 36/60] revert stuff --- asyncgit/src/sync/hooks.rs | 19 ++++++++++--------- git2-hooks/src/lib.rs | 6 +++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 6f0b4dbec2..2067861cc5 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -104,9 +104,9 @@ mod tests { let root = repo.path().parent().unwrap(); let hook = b"#!/bin/sh -echo 'rejected' -exit 1 -"; + echo 'rejected' + exit 1 + "; git2_hooks::create_hook( &repo, @@ -141,9 +141,9 @@ exit 1 crate::sync::utils::repo_work_dir(repo_path).unwrap(); let hook = b"#!/bin/sh -echo \"$(pwd)\" -exit 1 -"; + echo \"$(pwd)\" + exit 1 + "; git2_hooks::create_hook( &repo, git2_hooks::HOOK_PRE_COMMIT, @@ -166,9 +166,10 @@ exit 1 let root = repo.path().parent().unwrap(); let hook = b"#!/bin/sh -echo 'msg' > \"$1\" -echo 'rejected' -exit 1"; + echo 'msg' > \"$1\" + echo 'rejected' + exit 1 + "; git2_hooks::create_hook( &repo, diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index e942fea531..66538d9a5c 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -532,9 +532,9 @@ sys.exit(1) let (_td, repo) = repo_init(); let hook = b"#!/bin/sh -echo 'msg' > \"$1\" -echo 'rejected' -exit 1 + echo 'msg' > \"$1\" + echo 'rejected' + exit 1 "; create_hook(&repo, HOOK_COMMIT_MSG, hook); From adeb50dbd61f865273b85e03f34784b242346e23 Mon Sep 17 00:00:00 2001 From: Joshix Date: Tue, 25 Mar 2025 21:00:00 +0000 Subject: [PATCH 37/60] trim_ascii_end --- git2-hooks/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index 66538d9a5c..9a67e29e9f 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -334,9 +334,9 @@ exit 0 } = result { assert_eq!(code, Some(42)); - assert_eq!(stdout.as_str().trim_end(), TEXT); - assert!(stderr.is_empty()); assert_eq!(h, hook.hook); + assert_eq!(stdout.as_str().trim_ascii_end(), TEXT); + assert!(stderr.is_empty()); } else { panic!("run_hook should've failed"); } From c5c1fe99e0aadb49ebb180cf4726cfce3f3eb0a6 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 17:00:00 +0000 Subject: [PATCH 38/60] format assert --- git2-hooks/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index 9a67e29e9f..a9739ecfe3 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -335,7 +335,14 @@ exit 0 { assert_eq!(code, Some(42)); assert_eq!(h, hook.hook); - assert_eq!(stdout.as_str().trim_ascii_end(), TEXT); + assert_eq!( + stdout.as_str().trim_ascii_end(), + TEXT, + "{:?} != {TEXT:?} | {:?} != {:?}", + stdout.as_str().trim_ascii_end(), + stdout.as_str().trim_ascii_end().as_bytes(), + TEXT.as_bytes() + ); assert!(stderr.is_empty()); } else { panic!("run_hook should've failed"); From c0a8996a954ea30cf4d0c1a933c4cf95d983552b Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 17:00:00 +0000 Subject: [PATCH 39/60] move truncation --- asyncgit/src/sync/hooks.rs | 16 +--------------- git2-hooks/src/hookspath.rs | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 2067861cc5..384d222968 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -21,21 +21,7 @@ impl From for HookResult { stdout, stderr, .. - } => { - let mut combined = format!("{stdout}{stderr}"); - - if cfg!(windows) { - const ANSI_CLEAR: &str = "\x1B[H\x1B[2J\x1B[3J"; - - if let Some(trimmed) = - combined.strip_suffix(ANSI_CLEAR) - { - combined.truncate(trimmed.len()); - } - } - - Self::NotOk(combined) - } + } => Self::NotOk(format!("{stdout}{stderr}")), } } } diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index f46abac734..fd1ed215df 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -134,11 +134,23 @@ impl HookPaths { if output.status.success() { Ok(HookResult::Ok { hook }) } else { - let stderr = + let mut stderr = String::from_utf8_lossy(&output.stderr).to_string(); - let stdout = + let mut stdout = String::from_utf8_lossy(&output.stdout).to_string(); + if cfg!(windows) { + const ANSI_CLEAR: &str = "\x1B[H\x1B[2J\x1B[3J"; + + for text in [&mut stderr, &mut stdout] { + if let Some(trimmed) = + text.strip_suffix(ANSI_CLEAR) + { + text.truncate(trimmed.len()); + } + } + } + Ok(HookResult::RunNotSuccessful { code: output.status.code(), stdout, From 3e4b3c21b926a28f67d9a0b92c3bfc0819cd2534 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 17:00:00 +0000 Subject: [PATCH 40/60] debug --- git2-hooks/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index a9739ecfe3..28d64b2d62 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -442,9 +442,12 @@ exit 1 unreachable!() }; - assert!(stdout - .lines() - .any(|line| line.starts_with("export PATH"))); + assert!( + stdout + .lines() + .any(|line| line.starts_with("export PATH")), + "{stdout:?}" + ); } #[test] @@ -510,7 +513,7 @@ sys.exit(0) create_hook(&repo, HOOK_PRE_COMMIT, hook); let res = hooks_pre_commit(&repo, None).unwrap(); - assert!(res.is_ok()); + assert!(res.is_ok(), "{res:?}"); } #[test] From db60ac3c92d75e722ad0c98af4e24ea15c720ec8 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 17:00:00 +0000 Subject: [PATCH 41/60] fix test --- git2-hooks/src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index 28d64b2d62..a13fea5bf7 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -428,6 +428,12 @@ exit 1 #[test] fn test_env_containing_path() { + const PATH_EXPORT: &str = if cfg!(windows) { + "declare -x PATH=" + } else { + "export PATH" + }; + let (_td, repo) = repo_init(); let hook = b"#!/bin/sh @@ -445,8 +451,8 @@ exit 1 assert!( stdout .lines() - .any(|line| line.starts_with("export PATH")), - "{stdout:?}" + .any(|line| line.starts_with(PATH_EXPORT)), + "Could not find line starting with {PATH_EXPORT:?} in: {stdout:?}" ); } From 5a1dfbda57b89ac8734cd6c1c4c8ea26e161fe19 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 18:00:00 +0000 Subject: [PATCH 42/60] fix windows --- git2-hooks/src/hookspath.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index fd1ed215df..7e4dc2906d 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -110,7 +110,7 @@ impl HookPaths { let hook = self.hook.clone(); log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); - let run_command = |mut command: Command| { + let run_command = |command: &mut Command| { command .args(args) .current_dir(&self.pwd) @@ -119,13 +119,26 @@ impl HookPaths { }; let output = if cfg!(windows) { - // execute hook with sh - run_command(sh_command(&hook)) + // execute hook in shell + let command = { + let mut os_str = std::ffi::OsString::new(); + os_str.push("'"); + if let Some(hook) = hook.to_str() { + os_str.push(hook.replace('\'', "\\'")); + } else { + os_str.push(hook.as_os_str()); // TODO: this doesn't work if `hook` contains single-quotes + } + os_str.push("'"); + os_str.push(" \"$@\""); + + os_str + }; + run_command(sh_command().arg(command)) } else { // execute hook directly - match run_command(Command::new(&hook)) { + match run_command(&mut Command::new(&hook)) { Err(err) if err.raw_os_error() == Some(ENOEXEC) => { - run_command(sh_command(&hook)) + run_command(sh_command().arg(&hook)) } result => result, } @@ -161,7 +174,7 @@ impl HookPaths { } } -fn sh_command(script: &Path) -> Command { +fn sh_command() -> Command { let mut command = Command::new(sh_path()); if cfg!(windows) { @@ -177,8 +190,6 @@ fn sh_command(script: &Path) -> Command { command.arg("-l"); } - command.arg(script); - command } From 94b9dc7d56224207191c557c04d9fe64cb150095 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 18:00:00 +0000 Subject: [PATCH 43/60] add missing argument --- git2-hooks/src/hookspath.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 7e4dc2906d..f04940b46a 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -133,7 +133,7 @@ impl HookPaths { os_str }; - run_command(sh_command().arg(command)) + run_command(sh_command().arg("-c").arg(command)) } else { // execute hook directly match run_command(&mut Command::new(&hook)) { From f9ff366bcc828d9a8e807f8318d0157380acf491 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 18:00:00 +0000 Subject: [PATCH 44/60] add argument --- git2-hooks/src/hookspath.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index f04940b46a..740f009a64 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -133,7 +133,9 @@ impl HookPaths { os_str }; - run_command(sh_command().arg("-c").arg(command)) + run_command( + sh_command().arg("-c").arg(command).arg(&hook), + ) } else { // execute hook directly match run_command(&mut Command::new(&hook)) { From 38a34840c02e9290ea8173c273feb1e20d242124 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 19:00:00 +0000 Subject: [PATCH 45/60] remove hacks --- git2-hooks/src/hookspath.rs | 16 ++-------------- git2-hooks/src/lib.rs | 6 +----- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 740f009a64..c88c08188a 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -149,23 +149,11 @@ impl HookPaths { if output.status.success() { Ok(HookResult::Ok { hook }) } else { - let mut stderr = + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); - let mut stdout = + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); - if cfg!(windows) { - const ANSI_CLEAR: &str = "\x1B[H\x1B[2J\x1B[3J"; - - for text in [&mut stderr, &mut stdout] { - if let Some(trimmed) = - text.strip_suffix(ANSI_CLEAR) - { - text.truncate(trimmed.len()); - } - } - } - Ok(HookResult::RunNotSuccessful { code: output.status.code(), stdout, diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index a13fea5bf7..022aa4622e 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -428,11 +428,7 @@ exit 1 #[test] fn test_env_containing_path() { - const PATH_EXPORT: &str = if cfg!(windows) { - "declare -x PATH=" - } else { - "export PATH" - }; + const PATH_EXPORT: &str = "export PATH"; let (_td, repo) = repo_init(); From 7b4903b97500aee394a778ed45bc2c9babadb2e0 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 19:00:00 +0000 Subject: [PATCH 46/60] correctly escape single-quote if utf8 --- asyncgit/src/sync/hooks.rs | 2 +- git2-hooks/src/hookspath.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 384d222968..85b40d3c71 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -81,7 +81,7 @@ mod tests { use crate::sync::tests::repo_init_with_prefix; fn repo_init() -> Result<(TempDir, Repository)> { - repo_init_with_prefix("gitui $# ") + repo_init_with_prefix("gitui $# ' ") } #[test] diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index c88c08188a..ea6ce9e189 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -124,7 +124,15 @@ impl HookPaths { let mut os_str = std::ffi::OsString::new(); os_str.push("'"); if let Some(hook) = hook.to_str() { - os_str.push(hook.replace('\'', "\\'")); + // SEE: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_02 + // Enclosing characters in single-quotes ( '' ) shall preserve the literal value of each character within the single-quotes. + // A single-quote cannot occur within single-quotes. + const REPLACEMENT: &str = concat!( + "'", // closing single-quote + "\\'", // one escaped single-quote (outside of single-quotes) + "'", // new single-quote + ); + os_str.push(hook.replace('\'', REPLACEMENT)); } else { os_str.push(hook.as_os_str()); // TODO: this doesn't work if `hook` contains single-quotes } From 0b3d2c0d000f5051c60528a8b5fa9855ab8e2460 Mon Sep 17 00:00:00 2001 From: Joshix Date: Wed, 26 Mar 2025 19:00:00 +0000 Subject: [PATCH 47/60] less indentation --- git2-hooks/src/lib.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index 022aa4622e..fea3fc3bf3 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -326,27 +326,22 @@ exit 0 let result = hook.run_hook(&[TEXT]).unwrap(); - if let HookResult::RunNotSuccessful { + let HookResult::RunNotSuccessful { code, stdout, stderr, hook: h, } = result - { - assert_eq!(code, Some(42)); - assert_eq!(h, hook.hook); - assert_eq!( - stdout.as_str().trim_ascii_end(), - TEXT, - "{:?} != {TEXT:?} | {:?} != {:?}", - stdout.as_str().trim_ascii_end(), - stdout.as_str().trim_ascii_end().as_bytes(), - TEXT.as_bytes() - ); - assert!(stderr.is_empty()); - } else { - panic!("run_hook should've failed"); - } + else { + unreachable!("run_hook should've failed"); + }; + + let stdout = stdout.as_str().trim_ascii_end(); + + assert_eq!(code, Some(42)); + assert_eq!(h, hook.hook); + assert_eq!(stdout, TEXT, "{:?} != {TEXT:?}", stdout); + assert!(stderr.is_empty()); } #[test] From a0b7503afacf9602cd6a93f5f46615bf0b6855ec Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 17:00:00 +0000 Subject: [PATCH 48/60] test if string contains single-quote --- git2-hooks/src/hookspath.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index ea6ce9e189..8b4d2d32fc 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -134,7 +134,22 @@ impl HookPaths { ); os_str.push(hook.replace('\'', REPLACEMENT)); } else { - os_str.push(hook.as_os_str()); // TODO: this doesn't work if `hook` contains single-quotes + #[cfg(windows)] + { + use std::os::windows::ffi::OsStrExt; + if hook + .as_os_str() + .encode_wide() + .into_iter() + .find(|x| *x == (b'\'' as u16)) + .is_some() + { + // TODO: escape single quotes instead of failing + return Err(HooksError::PathToString); + } + } + + os_str.push(hook.as_os_str()); } os_str.push("'"); os_str.push(" \"$@\""); From f58f5507cd6314dcc38bdd2b387526395f28d56a Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 18:00:00 +0000 Subject: [PATCH 49/60] replace single-quotes in string --- git2-hooks/src/hookspath.rs | 49 +++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 8b4d2d32fc..8137c75dbb 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -3,6 +3,7 @@ use git2::Repository; use crate::{error::Result, HookResult, HooksError}; use std::{ + ffi::OsString, path::{Path, PathBuf}, process::Command, str::FromStr, @@ -121,35 +122,41 @@ impl HookPaths { let output = if cfg!(windows) { // execute hook in shell let command = { - let mut os_str = std::ffi::OsString::new(); + // SEE: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_02 + // Enclosing characters in single-quotes ( '' ) shall preserve the literal value of each character within the single-quotes. + // A single-quote cannot occur within single-quotes. + const REPLACEMENT: &str = concat!( + "'", // closing single-quote + "\\'", // one escaped single-quote (outside of single-quotes) + "'", // new single-quote + ); + + let mut os_str = OsString::new(); os_str.push("'"); if let Some(hook) = hook.to_str() { - // SEE: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_02 - // Enclosing characters in single-quotes ( '' ) shall preserve the literal value of each character within the single-quotes. - // A single-quote cannot occur within single-quotes. - const REPLACEMENT: &str = concat!( - "'", // closing single-quote - "\\'", // one escaped single-quote (outside of single-quotes) - "'", // new single-quote - ); os_str.push(hook.replace('\'', REPLACEMENT)); } else { #[cfg(windows)] { - use std::os::windows::ffi::OsStrExt; - if hook - .as_os_str() - .encode_wide() - .into_iter() - .find(|x| *x == (b'\'' as u16)) - .is_some() - { - // TODO: escape single quotes instead of failing - return Err(HooksError::PathToString); + use std::os::windows::ffi::{ + OsStrExt, OsStringExt, + }; + + let mut new_str: Vec = vec![]; + + for ch in hook.as_os_str().encode_wide() { + if ch == (b'\'' as u16) { + new_str.extend( + std::ffi::OsStr::new(REPLACEMENT) + .encode_wide(), + ); + } else { + new_str.push(ch); + } } - } - os_str.push(hook.as_os_str()); + os_str.push(OsString::from_wide(&new_str)); + } } os_str.push("'"); os_str.push(" \"$@\""); From 9b8a87e6da0426440292e07db3412cbe206597a6 Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 18:00:00 +0000 Subject: [PATCH 50/60] add test with invalid utf8 --- asyncgit/src/sync/hooks.rs | 29 ++++++++++++++++++++++++++++- asyncgit/src/sync/mod.rs | 4 ++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 85b40d3c71..6606b43ae0 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -74,6 +74,8 @@ pub fn hooks_prepare_commit_msg( #[cfg(test)] mod tests { + use std::ffi::{OsStr, OsString}; + use git2::Repository; use tempfile::TempDir; @@ -81,7 +83,32 @@ mod tests { use crate::sync::tests::repo_init_with_prefix; fn repo_init() -> Result<(TempDir, Repository)> { - repo_init_with_prefix("gitui $# ' ") + const INVALID_UTF8: &[u8] = b"\xED\xA0\x80"; + let mut os_string: OsString = OsString::new(); + + os_string.push("gitui $# ' "); + + #[cfg(windows)] + { + use std::os::windows::ffi::OsStringExt; + + const INVALID_UTF8: &[u16] = + INVALID_UTF8.map(|b| b as u16); + + os_str.push(OsString::from_wide(INVALID_UTF8)); + + assert!(os_string.to_str().is_none()); + } + #[cfg(unix)] + { + use std::os::unix::ffi::OsStrExt; + + os_string.push(OsStr::from_bytes(INVALID_UTF8)); + + assert!(os_string.to_str().is_none()); + } + + repo_init_with_prefix(os_string) } #[test] diff --git a/asyncgit/src/sync/mod.rs b/asyncgit/src/sync/mod.rs index 683aacd0ef..09d4e6ef53 100644 --- a/asyncgit/src/sync/mod.rs +++ b/asyncgit/src/sync/mod.rs @@ -123,7 +123,7 @@ pub mod tests { }; use crate::error::Result; use git2::Repository; - use std::{path::Path, process::Command}; + use std::{ffi::OsStr, path::Path, process::Command}; use tempfile::TempDir; /// @@ -150,7 +150,7 @@ pub mod tests { /// #[inline] pub fn repo_init_with_prefix( - prefix: &'static str, + prefix: impl AsRef, ) -> Result<(TempDir, Repository)> { init_log(); From 418d01620c7ee2c22cb5776d7bf5ccfb2afead44 Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 19:00:00 +0000 Subject: [PATCH 51/60] fix tests with invalid utf8 --- asyncgit/src/sync/hooks.rs | 23 ++++++++++------------- asyncgit/src/sync/repository.rs | 6 ++++++ git2-hooks/src/hookspath.rs | 12 +++++++++++- git2-hooks/src/lib.rs | 5 +---- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 6606b43ae0..1d59049ce9 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -130,9 +130,7 @@ mod tests { let subfolder = root.join("foo/"); std::fs::create_dir_all(&subfolder).unwrap(); - let res = - hooks_post_commit(&subfolder.to_str().unwrap().into()) - .unwrap(); + let res = hooks_post_commit(&subfolder.into()).unwrap(); assert_eq!( res, @@ -148,10 +146,11 @@ mod tests { fn test_pre_commit_workdir() { let (_td, repo) = repo_init().unwrap(); let root = repo.path().parent().unwrap(); - let repo_path: &RepoPath = - &root.as_os_str().to_str().unwrap().into(); + let repo_path: &RepoPath = &root.to_path_buf().into(); + let repository = + crate::sync::repository::repo(repo_path).unwrap(); let workdir = - crate::sync::utils::repo_work_dir(repo_path).unwrap(); + crate::sync::utils::work_dir(&repository).unwrap(); let hook = b"#!/bin/sh echo \"$(pwd)\" @@ -165,8 +164,9 @@ mod tests { let res = hooks_pre_commit(repo_path).unwrap(); if let HookResult::NotOk(res) = res { assert_eq!( - std::path::Path::new(res.trim_end()), - std::path::Path::new(&workdir) + res.trim_end().trim_end_matches('/'), + // TODO: fix if output isn't utf8. + workdir.to_string_lossy().trim_end_matches('/'), ); } else { assert!(false); @@ -194,11 +194,8 @@ mod tests { std::fs::create_dir_all(&subfolder).unwrap(); let mut msg = String::from("test"); - let res = hooks_commit_msg( - &subfolder.to_str().unwrap().into(), - &mut msg, - ) - .unwrap(); + let res = + hooks_commit_msg(&subfolder.into(), &mut msg).unwrap(); assert_eq!( res, diff --git a/asyncgit/src/sync/repository.rs b/asyncgit/src/sync/repository.rs index 2a0af47dbd..ea251c5e46 100644 --- a/asyncgit/src/sync/repository.rs +++ b/asyncgit/src/sync/repository.rs @@ -42,6 +42,12 @@ impl RepoPath { } } +impl From for RepoPath { + fn from(value: PathBuf) -> Self { + Self::Path(value) + } +} + impl From<&str> for RepoPath { fn from(p: &str) -> Self { Self::Path(PathBuf::from(p)) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 8137c75dbb..36389584f3 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -3,7 +3,7 @@ use git2::Repository; use crate::{error::Result, HookResult, HooksError}; use std::{ - ffi::OsString, + ffi::{OsStr, OsString}, path::{Path, PathBuf}, process::Command, str::FromStr, @@ -108,6 +108,16 @@ impl HookPaths { /// this function calls hook scripts based on conventions documented here /// see pub fn run_hook(&self, args: &[&str]) -> Result { + self.run_hook_os_str(args) + } + + /// this function calls hook scripts based on conventions documented here + /// see + pub fn run_hook_os_str(&self, args: I) -> Result + where + I: IntoIterator + Copy, + S: AsRef, + { let hook = self.hook.clone(); log::trace!("run hook '{:?}' in '{:?}'", hook, self.pwd); diff --git a/git2-hooks/src/lib.rs b/git2-hooks/src/lib.rs index fea3fc3bf3..4c949a1e46 100644 --- a/git2-hooks/src/lib.rs +++ b/git2-hooks/src/lib.rs @@ -132,10 +132,7 @@ pub fn hooks_commit_msg( let temp_file = hook.git.join(HOOK_COMMIT_MSG_TEMP_FILE); File::create(&temp_file)?.write_all(msg.as_bytes())?; - let res = hook.run_hook(&[temp_file - .as_os_str() - .to_string_lossy() - .as_ref()])?; + let res = hook.run_hook_os_str([&temp_file])?; // load possibly altered msg msg.clear(); From f2d41fdbd86ce823037ebb7be800775dfa56f3a6 Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 19:00:00 +0000 Subject: [PATCH 52/60] fix generating invalid utf8 ostring --- asyncgit/src/sync/hooks.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 1d59049ce9..23b65982c3 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -84,6 +84,7 @@ mod tests { fn repo_init() -> Result<(TempDir, Repository)> { const INVALID_UTF8: &[u8] = b"\xED\xA0\x80"; + let mut os_string: OsString = OsString::new(); os_string.push("gitui $# ' "); @@ -92,14 +93,16 @@ mod tests { { use std::os::windows::ffi::OsStringExt; - const INVALID_UTF8: &[u16] = - INVALID_UTF8.map(|b| b as u16); + let invalid_utf8 = INVALID_UTF8 + .into_iter() + .map(|b| b as u16) + .collect::>(); - os_str.push(OsString::from_wide(INVALID_UTF8)); + os_string.push(OsString::from_wide(&invalid_utf8)); assert!(os_string.to_str().is_none()); } - #[cfg(unix)] + #[cfg(target_os = "linux")] { use std::os::unix::ffi::OsStrExt; From 40179d52c62096f758f4fa84d4a7b579c3ccf14c Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 19:00:00 +0000 Subject: [PATCH 53/60] remove dead_code --- asyncgit/src/sync/hooks.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 23b65982c3..4c6d94335c 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -74,7 +74,7 @@ pub fn hooks_prepare_commit_msg( #[cfg(test)] mod tests { - use std::ffi::{OsStr, OsString}; + use std::ffi::OsString; use git2::Repository; use tempfile::TempDir; @@ -83,6 +83,7 @@ mod tests { use crate::sync::tests::repo_init_with_prefix; fn repo_init() -> Result<(TempDir, Repository)> { + #[allow(dead_code)] const INVALID_UTF8: &[u8] = b"\xED\xA0\x80"; let mut os_string: OsString = OsString::new(); @@ -106,7 +107,7 @@ mod tests { { use std::os::unix::ffi::OsStrExt; - os_string.push(OsStr::from_bytes(INVALID_UTF8)); + os_string.push(std::ffi::OsStr::from_bytes(INVALID_UTF8)); assert!(os_string.to_str().is_none()); } From 16e150e3b85f4b052e536207a266209c25bce1c2 Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 19:00:00 +0000 Subject: [PATCH 54/60] dereference --- asyncgit/src/sync/hooks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 4c6d94335c..c79ab74f41 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -96,7 +96,7 @@ mod tests { let invalid_utf8 = INVALID_UTF8 .into_iter() - .map(|b| b as u16) + .map(|b| *b as u16) .collect::>(); os_string.push(OsString::from_wide(&invalid_utf8)); From 2796f8f2120289d124dc98bb760d903d172bc1d2 Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 19:00:00 +0000 Subject: [PATCH 55/60] try different string on windows --- asyncgit/src/sync/hooks.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index c79ab74f41..57bf95f220 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -83,9 +83,6 @@ mod tests { use crate::sync::tests::repo_init_with_prefix; fn repo_init() -> Result<(TempDir, Repository)> { - #[allow(dead_code)] - const INVALID_UTF8: &[u8] = b"\xED\xA0\x80"; - let mut os_string: OsString = OsString::new(); os_string.push("gitui $# ' "); @@ -94,12 +91,9 @@ mod tests { { use std::os::windows::ffi::OsStringExt; - let invalid_utf8 = INVALID_UTF8 - .into_iter() - .map(|b| *b as u16) - .collect::>(); + const INVALID_UTF16: &[u16] = &[0xD83D]; - os_string.push(OsString::from_wide(&invalid_utf8)); + os_string.push(OsString::from_wide(INVALID_UTF16)); assert!(os_string.to_str().is_none()); } @@ -107,11 +101,15 @@ mod tests { { use std::os::unix::ffi::OsStrExt; + const INVALID_UTF8: &[u8] = b"\xED\xA0\x80"; + os_string.push(std::ffi::OsStr::from_bytes(INVALID_UTF8)); assert!(os_string.to_str().is_none()); } + os_string.push(" "); + repo_init_with_prefix(os_string) } From ee8fcf96b5cad4164172ce010bc210bd5f733b0f Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 19:00:00 +0000 Subject: [PATCH 56/60] only valid unicode paths are accepted on windows --- asyncgit/src/sync/hooks.rs | 10 ---------- git2-hooks/src/hookspath.rs | 24 ++---------------------- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/asyncgit/src/sync/hooks.rs b/asyncgit/src/sync/hooks.rs index 57bf95f220..e5fa60346d 100644 --- a/asyncgit/src/sync/hooks.rs +++ b/asyncgit/src/sync/hooks.rs @@ -87,16 +87,6 @@ mod tests { os_string.push("gitui $# ' "); - #[cfg(windows)] - { - use std::os::windows::ffi::OsStringExt; - - const INVALID_UTF16: &[u16] = &[0xD83D]; - - os_string.push(OsString::from_wide(INVALID_UTF16)); - - assert!(os_string.to_str().is_none()); - } #[cfg(target_os = "linux")] { use std::os::unix::ffi::OsStrExt; diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 36389584f3..797a4a6ab0 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -1,4 +1,4 @@ -use git2::Repository; +use git2::{IntoCString, Repository}; use crate::{error::Result, HookResult, HooksError}; @@ -146,27 +146,7 @@ impl HookPaths { if let Some(hook) = hook.to_str() { os_str.push(hook.replace('\'', REPLACEMENT)); } else { - #[cfg(windows)] - { - use std::os::windows::ffi::{ - OsStrExt, OsStringExt, - }; - - let mut new_str: Vec = vec![]; - - for ch in hook.as_os_str().encode_wide() { - if ch == (b'\'' as u16) { - new_str.extend( - std::ffi::OsStr::new(REPLACEMENT) - .encode_wide(), - ); - } else { - new_str.push(ch); - } - } - - os_str.push(OsString::from_wide(&new_str)); - } + return Err(HooksError::PathToString); } os_str.push("'"); os_str.push(" \"$@\""); From eaad1f33d619b54d88b923e801b75ad1d103d35f Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 19:00:00 +0000 Subject: [PATCH 57/60] remove unused import --- git2-hooks/src/hookspath.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 797a4a6ab0..b2d21d58ab 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -1,4 +1,4 @@ -use git2::{IntoCString, Repository}; +use git2::Repository; use crate::{error::Result, HookResult, HooksError}; From 48922ce0582fa1cbe4a0435695b31c4cc178498b Mon Sep 17 00:00:00 2001 From: Joshix Date: Sun, 30 Mar 2025 13:00:00 +0000 Subject: [PATCH 58/60] revert returning error --- git2-hooks/src/hookspath.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index b2d21d58ab..34fc77cd40 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -146,7 +146,7 @@ impl HookPaths { if let Some(hook) = hook.to_str() { os_str.push(hook.replace('\'', REPLACEMENT)); } else { - return Err(HooksError::PathToString); + os_str.push(hook.as_os_str()); // TODO: this doesn't work if `hook` contains single-quotes } os_str.push("'"); os_str.push(" \"$@\""); From 9649e849e774265612c376c0b21c9b72e9b0ecfd Mon Sep 17 00:00:00 2001 From: Joshix Date: Thu, 27 Mar 2025 17:00:00 +0000 Subject: [PATCH 59/60] test if string contains single-quote --- git2-hooks/src/hookspath.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 34fc77cd40..4ba2cd6f38 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -146,7 +146,22 @@ impl HookPaths { if let Some(hook) = hook.to_str() { os_str.push(hook.replace('\'', REPLACEMENT)); } else { - os_str.push(hook.as_os_str()); // TODO: this doesn't work if `hook` contains single-quotes + #[cfg(windows)] + { + use std::os::windows::ffi::OsStrExt; + if hook + .as_os_str() + .encode_wide() + .into_iter() + .find(|x| *x == (b'\'' as u16)) + .is_some() + { + // TODO: escape single quotes instead of failing + return Err(HooksError::PathToString); + } + } + + os_str.push(hook.as_os_str()); } os_str.push("'"); os_str.push(" \"$@\""); From c74e7627c0c616e50db5bed576c2875bf92e749b Mon Sep 17 00:00:00 2001 From: Joshix Date: Sun, 30 Mar 2025 13:00:00 +0000 Subject: [PATCH 60/60] make clippy happy --- git2-hooks/src/hookspath.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/git2-hooks/src/hookspath.rs b/git2-hooks/src/hookspath.rs index 4ba2cd6f38..ee475cf7f5 100644 --- a/git2-hooks/src/hookspath.rs +++ b/git2-hooks/src/hookspath.rs @@ -152,9 +152,7 @@ impl HookPaths { if hook .as_os_str() .encode_wide() - .into_iter() - .find(|x| *x == (b'\'' as u16)) - .is_some() + .any(|x| x == u16::from(b'\'')) { // TODO: escape single quotes instead of failing return Err(HooksError::PathToString);