mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
parent
7761336963
commit
19a0cb2db6
4 changed files with 79 additions and 52 deletions
|
|
@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- introduced proper changelog
|
- introduced proper changelog
|
||||||
|
- hook support on windows ([#14](https://github.com/extrawurst/gitui/issues/14))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- show longer commit messages in log view
|
- show longer commit messages in log view
|
||||||
|
|
|
||||||
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -33,7 +33,6 @@ version = "0.2.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"git2",
|
"git2",
|
||||||
"is_executable",
|
|
||||||
"log",
|
"log",
|
||||||
"rayon-core",
|
"rayon-core",
|
||||||
"scopetime",
|
"scopetime",
|
||||||
|
|
@ -335,15 +334,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_executable"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "302d553b8abc8187beb7d663e34c065ac4570b273bc9511a50e940e99409c577"
|
|
||||||
dependencies = [
|
|
||||||
"winapi 0.3.8",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ git2 = { version = "0.13.5", default-features = false }
|
||||||
rayon-core = "1.7"
|
rayon-core = "1.7"
|
||||||
crossbeam-channel = "0.4"
|
crossbeam-channel = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
is_executable = "0.1"
|
|
||||||
scopetime = { path = "../scopetime", version = "0.1" }
|
scopetime = { path = "../scopetime", version = "0.1" }
|
||||||
tempfile = "3.1"
|
thiserror = "1.0"
|
||||||
thiserror = "1.0"
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3.1"
|
||||||
|
|
@ -1,17 +1,21 @@
|
||||||
use crate::error::{Error, Result};
|
use crate::error::Result;
|
||||||
use is_executable::IsExecutable;
|
|
||||||
use scopetime::scope_time;
|
use scopetime::scope_time;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::{
|
use std::{
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
path::Path,
|
path::Path,
|
||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
|
|
||||||
const HOOK_POST_COMMIT: &str = ".git/hooks/post-commit";
|
const HOOK_POST_COMMIT: &str = ".git/hooks/post-commit";
|
||||||
const HOOK_COMMIT_MSG: &str = ".git/hooks/commit-msg";
|
const HOOK_COMMIT_MSG: &str = ".git/hooks/commit-msg";
|
||||||
|
const HOOK_COMMIT_MSG_TEMP_FILE: &str = ".git/COMMIT_EDITMSG";
|
||||||
|
|
||||||
///
|
/// this hook is documented here https://git-scm.com/docs/githooks#_commit_msg
|
||||||
|
/// we use the same convention as other git clients to create a temp file containing
|
||||||
|
/// the commit message at `.git/COMMIT_EDITMSG` and pass it's relative path as the only
|
||||||
|
/// parameter to the hook script.
|
||||||
pub fn hooks_commit_msg(
|
pub fn hooks_commit_msg(
|
||||||
repo_path: &str,
|
repo_path: &str,
|
||||||
msg: &mut String,
|
msg: &mut String,
|
||||||
|
|
@ -19,23 +23,19 @@ pub fn hooks_commit_msg(
|
||||||
scope_time!("hooks_commit_msg");
|
scope_time!("hooks_commit_msg");
|
||||||
|
|
||||||
if hook_runable(repo_path, HOOK_COMMIT_MSG) {
|
if hook_runable(repo_path, HOOK_COMMIT_MSG) {
|
||||||
let mut file = NamedTempFile::new()?;
|
let temp_file =
|
||||||
|
Path::new(repo_path).join(HOOK_COMMIT_MSG_TEMP_FILE);
|
||||||
|
File::create(&temp_file)?.write_all(msg.as_bytes())?;
|
||||||
|
|
||||||
write!(file, "{}", msg)?;
|
let res = run_hook(
|
||||||
|
repo_path,
|
||||||
let file_path = file.path().to_str().ok_or_else(|| {
|
HOOK_COMMIT_MSG,
|
||||||
Error::Generic(
|
&[HOOK_COMMIT_MSG_TEMP_FILE],
|
||||||
"temp file path contains invalid unicode sequences."
|
);
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let res = run_hook(repo_path, HOOK_COMMIT_MSG, &[&file_path]);
|
|
||||||
|
|
||||||
// load possibly altered msg
|
// load possibly altered msg
|
||||||
let mut file = file.reopen()?;
|
|
||||||
msg.clear();
|
msg.clear();
|
||||||
file.read_to_string(msg)?;
|
File::open(temp_file)?.read_to_string(msg)?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -58,7 +58,7 @@ fn hook_runable(path: &str, hook: &str) -> bool {
|
||||||
let path = Path::new(path);
|
let path = Path::new(path);
|
||||||
let path = path.join(hook);
|
let path = path.join(hook);
|
||||||
|
|
||||||
path.exists() && path.is_executable()
|
path.exists() && is_executable(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
@ -70,20 +70,36 @@ pub enum HookResult {
|
||||||
NotOk(String),
|
NotOk(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_hook(path: &str, cmd: &str, args: &[&str]) -> HookResult {
|
/// this function calls hook scripts based on conventions documented here
|
||||||
match Command::new(cmd).args(args).current_dir(path).output() {
|
/// https://git-scm.com/docs/githooks
|
||||||
Ok(output) => {
|
fn run_hook(
|
||||||
if output.status.success() {
|
path: &str,
|
||||||
HookResult::Ok
|
hook_script: &str,
|
||||||
} else {
|
args: &[&str],
|
||||||
let err = String::from_utf8_lossy(&output.stderr);
|
) -> HookResult {
|
||||||
let out = String::from_utf8_lossy(&output.stdout);
|
let mut bash_args = vec![hook_script.to_string()];
|
||||||
let formatted = format!("{}{}", out, err);
|
bash_args.extend_from_slice(
|
||||||
|
&args
|
||||||
|
.iter()
|
||||||
|
.map(|x| (*x).to_string())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
);
|
||||||
|
|
||||||
HookResult::NotOk(formatted)
|
let output = Command::new("bash")
|
||||||
}
|
.args(bash_args)
|
||||||
}
|
.current_dir(path)
|
||||||
Err(e) => HookResult::NotOk(format!("{}", e)),
|
.output();
|
||||||
|
|
||||||
|
let output = output.expect("general hook error");
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
HookResult::Ok
|
||||||
|
} else {
|
||||||
|
let err = String::from_utf8_lossy(&output.stderr);
|
||||||
|
let out = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let formatted = format!("{}{}", out, err);
|
||||||
|
|
||||||
|
HookResult::NotOk(formatted)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,15 +131,17 @@ mod tests {
|
||||||
.write_all(hook_script)
|
.write_all(hook_script)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Command::new("chmod")
|
#[cfg(not(windows))]
|
||||||
.args(&["+x", hook_path])
|
{
|
||||||
.current_dir(path)
|
Command::new("chmod")
|
||||||
.output()
|
.args(&["+x", hook_path])
|
||||||
.unwrap();
|
.current_dir(path)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(windows))]
|
|
||||||
fn test_hooks_commit_msg_ok() {
|
fn test_hooks_commit_msg_ok() {
|
||||||
let (_td, repo) = repo_init().unwrap();
|
let (_td, repo) = repo_init().unwrap();
|
||||||
let root = repo.path().parent().unwrap();
|
let root = repo.path().parent().unwrap();
|
||||||
|
|
@ -145,7 +163,6 @@ exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(windows))]
|
|
||||||
fn test_hooks_commit_msg() {
|
fn test_hooks_commit_msg() {
|
||||||
let (_td, repo) = repo_init().unwrap();
|
let (_td, repo) = repo_init().unwrap();
|
||||||
let root = repo.path().parent().unwrap();
|
let root = repo.path().parent().unwrap();
|
||||||
|
|
@ -172,7 +189,6 @@ exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(windows))]
|
|
||||||
fn test_commit_msg_no_block_but_alter() {
|
fn test_commit_msg_no_block_but_alter() {
|
||||||
let (_td, repo) = repo_init().unwrap();
|
let (_td, repo) = repo_init().unwrap();
|
||||||
let root = repo.path().parent().unwrap();
|
let root = repo.path().parent().unwrap();
|
||||||
|
|
@ -193,3 +209,22 @@ exit 0
|
||||||
assert_eq!(msg, String::from("msg\n"));
|
assert_eq!(msg, String::from("msg\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
fn is_executable(path: PathBuf) -> bool {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let metadata = match path.metadata() {
|
||||||
|
Ok(metadata) => metadata,
|
||||||
|
Err(_) => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let permissions = metadata.permissions();
|
||||||
|
permissions.mode() & 0o111 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
/// windows does not consider bash scripts to be executable so we consider everything
|
||||||
|
/// to be executable (which is not far from the truth for windows platform.)
|
||||||
|
fn is_executable(_: PathBuf) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue