1414
1515use crate :: git:: message:: GitMessage ;
1616use crate :: git:: repository:: Repository ;
17+ use std:: fs;
1718use std:: io:: Write ;
19+ use tracing:: trace;
1820
19- // Get environment variable with default value fallback
21+ /// Generic result type for utility functions
22+ pub type Result < T > = std:: result:: Result < T , Box < dyn std:: error:: Error > > ;
23+
24+ /// Get environment variable with default value fallback
2025pub mod env {
2126 use std:: env;
2227
@@ -84,7 +89,7 @@ impl OutputFormat {
8489 }
8590
8691 /// Write the message in the specified format
87- pub fn write ( & self , message : & GitMessage ) -> Result < ( ) , Box < dyn std :: error :: Error > > {
92+ pub fn write ( & self , message : & GitMessage ) -> Result < ( ) > {
8893 match self {
8994 Self :: Stdout => {
9095 writeln ! ( std:: io:: stdout( ) , "{}" , message) ?;
@@ -149,10 +154,7 @@ pub fn format_openai_error(error: async_openai::error::OpenAIError) -> String {
149154}
150155
151156/// Save content to a file
152- pub fn save_to_file (
153- path : & str ,
154- content : & dyn std:: fmt:: Display ,
155- ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
157+ pub fn save_to_file ( path : & str , content : & dyn std:: fmt:: Display ) -> Result < ( ) > {
156158 use std:: fs:: File ;
157159 use std:: io:: Write ;
158160
@@ -162,6 +164,36 @@ pub fn save_to_file(
162164 Ok ( ( ) )
163165}
164166
167+ /// Install the prepare-commit-msg git hook into the target repository.
168+ pub fn install_hook ( path : & str , name : & str , content : & str ) -> Result < ( ) > {
169+ let repo_dir =
170+ fs:: canonicalize ( path) . map_err ( |e| format ! ( "resolve repository path failed: {e}" ) ) ?;
171+ let git_dir = repo_dir. join ( ".git" ) ;
172+ if !git_dir. is_dir ( ) {
173+ return Err ( "not a git repository (missing .git directory)" . into ( ) ) ;
174+ }
175+
176+ let hooks_dir = git_dir. join ( "hooks" ) ;
177+ fs:: create_dir_all ( & hooks_dir) . map_err ( |e| format ! ( "create hooks dir failed: {e}" ) ) ?;
178+
179+ let hook_path = hooks_dir. join ( name) ;
180+ fs:: write ( & hook_path, content) . map_err ( |e| format ! ( "write hook file failed: {e}" ) ) ?;
181+
182+ #[ cfg( unix) ]
183+ {
184+ use std:: os:: unix:: fs:: PermissionsExt ;
185+ let mut perms = fs:: metadata ( & hook_path)
186+ . map_err ( |e| format ! ( "get hook metadata failed: {e}" ) ) ?
187+ . permissions ( ) ;
188+ perms. set_mode ( 0o755 ) ;
189+ fs:: set_permissions ( & hook_path, perms)
190+ . map_err ( |e| format ! ( "set executable permission failed: {e}" ) ) ?;
191+ }
192+
193+ trace ! ( "hook installed at {:?}" , hook_path) ;
194+ Ok ( ( ) )
195+ }
196+
165197#[ cfg( test) ]
166198mod tests {
167199 use super :: * ;
@@ -194,4 +226,10 @@ Signed-off-by: mingcheng <mingcheng@apache.org>
194226 let result = env:: get ( "NONEXISTENT_VAR_XYZ" , "default_value" ) ;
195227 assert_eq ! ( result, "default_value" ) ;
196228 }
229+
230+ #[ test]
231+ fn test_install_hook ( ) {
232+ let result = install_hook ( "." , "test-hook" , "#!/bin/sh\n echo 'Test Hook'" ) ;
233+ assert ! ( result. is_ok( ) ) ;
234+ }
197235}
0 commit comments