2
2
3
3
//! Support for using git repository hooks.
4
4
5
- use std:: { borrow:: Cow , io:: Write , path:: PathBuf } ;
5
+ use std:: {
6
+ borrow:: Cow ,
7
+ io:: Write ,
8
+ path:: { Path , PathBuf } ,
9
+ } ;
6
10
7
11
use anyhow:: { anyhow, Context , Result } ;
8
12
@@ -108,28 +112,28 @@ pub(crate) fn run_commit_msg_hook<'repo>(
108
112
return Ok ( message) ;
109
113
} ;
110
114
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) ?;
114
117
115
118
let index_path = repo. index_path ( ) ;
116
119
117
120
// TODO: when git runs this hook, it only sets GIT_INDEX_FILE and sometimes
118
121
// GIT_EDITOR. So author and committer vars are not clearly required.
119
122
let mut hook_command = std:: process:: Command :: new ( hook_path) ;
123
+ hook_command. current_dir ( work_dir) ;
120
124
hook_command. env ( "GIT_INDEX_FILE" , & index_path) ;
121
125
if !use_editor {
122
126
hook_command. env ( "GIT_EDITOR" , ":" ) ;
123
127
}
124
128
125
- hook_command. arg ( & msg_file_path ) ;
129
+ hook_command. arg ( temp_msg . filename ( ) ) ;
126
130
127
131
let status = hook_command
128
132
. status ( )
129
133
. with_context ( || format ! ( "`{hook_name}` hook" ) ) ?;
130
134
131
135
if status. success ( ) {
132
- let message_bytes = std :: fs :: read ( & msg_file_path ) ?;
136
+ let message_bytes = temp_msg . read ( ) ?;
133
137
let encoding = message. encoding ( ) ?;
134
138
let message = encoding
135
139
. decode_without_bom_handling_and_without_replacement ( & message_bytes)
@@ -146,6 +150,53 @@ pub(crate) fn run_commit_msg_hook<'repo>(
146
150
}
147
151
}
148
152
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
+
149
200
#[ cfg( unix) ]
150
201
fn is_executable ( meta : & std:: fs:: Metadata ) -> bool {
151
202
use std:: os:: unix:: fs:: MetadataExt ;
0 commit comments