Skip to content

Commit 5f8cce4

Browse files
committed
fix: run commit-msg hook in work dir
The commit-msg hook is supposed to be run from the work tree root when not operating on a bare repository. Instead of using tempfile::NamedTempFile, we switch to creating our own temporary file relative to the work dir. This allows the temporary file argument to commit-msg to be a simple relative file name, which eases the complexity of passing the argument on Windows.
1 parent bf273d9 commit 5f8cce4

File tree

1 file changed

+57
-6
lines changed

1 file changed

+57
-6
lines changed

src/hook.rs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
//! Support for using git repository hooks.
44
5-
use std::{borrow::Cow, io::Write, path::PathBuf};
5+
use std::{
6+
borrow::Cow,
7+
io::Write,
8+
path::{Path, PathBuf},
9+
};
610

711
use anyhow::{anyhow, Context, Result};
812

@@ -108,28 +112,28 @@ pub(crate) fn run_commit_msg_hook<'repo>(
108112
return Ok(message);
109113
};
110114

111-
let mut msg_file = tempfile::NamedTempFile::new()?;
112-
msg_file.write_all(message.raw_bytes())?;
113-
let msg_file_path = msg_file.into_temp_path();
115+
let work_dir = repo.work_dir().expect("not a bare repo");
116+
let temp_msg = TemporaryMessage::new(work_dir, &message)?;
114117

115118
let index_path = repo.index_path();
116119

117120
// TODO: when git runs this hook, it only sets GIT_INDEX_FILE and sometimes
118121
// GIT_EDITOR. So author and committer vars are not clearly required.
119122
let mut hook_command = std::process::Command::new(hook_path);
123+
hook_command.current_dir(work_dir);
120124
hook_command.env("GIT_INDEX_FILE", &index_path);
121125
if !use_editor {
122126
hook_command.env("GIT_EDITOR", ":");
123127
}
124128

125-
hook_command.arg(&msg_file_path);
129+
hook_command.arg(temp_msg.filename());
126130

127131
let status = hook_command
128132
.status()
129133
.with_context(|| format!("`{hook_name}` hook"))?;
130134

131135
if status.success() {
132-
let message_bytes = std::fs::read(&msg_file_path)?;
136+
let message_bytes = temp_msg.read()?;
133137
let encoding = message.encoding()?;
134138
let message = encoding
135139
.decode_without_bom_handling_and_without_replacement(&message_bytes)
@@ -146,6 +150,53 @@ pub(crate) fn run_commit_msg_hook<'repo>(
146150
}
147151
}
148152

153+
/// Temporary commit message file for commit-msg hook.
154+
///
155+
/// The temporary file is created relative to the work dir using the StGit process id to
156+
/// avoid collisions with other StGit processes.
157+
struct TemporaryMessage<'repo> {
158+
work_dir: &'repo Path,
159+
filename: PathBuf,
160+
}
161+
162+
impl<'repo> TemporaryMessage<'repo> {
163+
/// Create new temporary file containing commit message.
164+
fn new(work_dir: &'repo Path, message: &Message<'repo>) -> Result<Self> {
165+
let pid = std::process::id();
166+
let filename = PathBuf::from(format!(".stgit-msg-temp-{pid}"));
167+
let msg_path = work_dir.join(&filename);
168+
let mut msg_file = std::fs::OpenOptions::new()
169+
.create_new(true)
170+
.write(true)
171+
.open(msg_path)?;
172+
msg_file.write_all(message.raw_bytes())?;
173+
Ok(Self { work_dir, filename })
174+
}
175+
176+
/// Get name of temporary message file.
177+
///
178+
/// This is not a complete path. The temporary file is relative to the work_dir.
179+
fn filename(&self) -> &Path {
180+
self.filename.as_ref()
181+
}
182+
183+
/// Read contents of temporary message file.
184+
fn read(&self) -> Result<Vec<u8>> {
185+
Ok(std::fs::read(self.work_dir.join(&self.filename))?)
186+
}
187+
}
188+
189+
impl<'repo> Drop for TemporaryMessage<'repo> {
190+
fn drop(&mut self) {
191+
let msg_path = self.work_dir.join(&self.filename);
192+
if msg_path.is_file() {
193+
if let Err(e) = std::fs::remove_file(&msg_path) {
194+
panic!("failed to remove temp message {msg_path:?}: {e}");
195+
}
196+
}
197+
}
198+
}
199+
149200
#[cfg(unix)]
150201
fn is_executable(meta: &std::fs::Metadata) -> bool {
151202
use std::os::unix::fs::MetadataExt;

0 commit comments

Comments
 (0)