@@ -52,6 +52,8 @@ type GitContext = {
5252 projectPath : string ;
5353} ;
5454
55+ type GitConfigScopeOption = '--local' | '--worktree' ;
56+
5557const fail = ( reason : string , message : string ) : FailedInstallResult => ( {
5658 status : 'failed' ,
5759 reason,
@@ -111,9 +113,78 @@ const gitFailure = (
111113 ) ;
112114} ;
113115
116+ const resolveHooksPathScope = (
117+ cwd : string ,
118+ ) : GitConfigScopeOption | FailedInstallResult => {
119+ const configured = runGit ( cwd , [
120+ 'config' ,
121+ '--show-scope' ,
122+ '--get' ,
123+ 'core.hooksPath' ,
124+ ] ) ;
125+ if ( configured . error || configured . status === null ) {
126+ return gitFailure ( configured . error , configured . stderr ) ;
127+ }
128+
129+ // Exit status 1 means core.hooksPath is not configured yet.
130+ if ( configured . status === 1 ) {
131+ return '--local' ;
132+ }
133+ if ( configured . status !== 0 ) {
134+ return fail (
135+ 'git-config-failed' ,
136+ `Failed to resolve the core.hooksPath scope: ${ configured . stderr . trim ( ) } ` ,
137+ ) ;
138+ }
139+
140+ const separator = configured . stdout . indexOf ( '\t' ) ;
141+ const scope = separator === - 1 ? '' : configured . stdout . slice ( 0 , separator ) ;
142+ if ( scope === 'worktree' ) {
143+ return '--worktree' ;
144+ }
145+ if ( scope === 'command' ) {
146+ return fail (
147+ 'hooks-path-command-scope' ,
148+ "Cannot configure core.hooksPath because it is set in Git's command scope. Remove the command-scoped override and rerun rs setup." ,
149+ ) ;
150+ }
151+ if ( scope === 'system' || scope === 'global' || scope === 'local' ) {
152+ return '--local' ;
153+ }
154+
155+ return fail (
156+ 'git-config-failed' ,
157+ 'Failed to resolve the core.hooksPath scope.' ,
158+ ) ;
159+ } ;
160+
161+ const resolveGitHooksPath = ( cwd : string ) : string | FailedInstallResult => {
162+ const hooksDirectory = runGit ( cwd , [
163+ 'rev-parse' ,
164+ '--path-format=absolute' ,
165+ '--git-path' ,
166+ 'hooks' ,
167+ ] ) ;
168+ if ( hooksDirectory . error || hooksDirectory . status === null ) {
169+ return gitFailure ( hooksDirectory . error , hooksDirectory . stderr ) ;
170+ }
171+ if ( hooksDirectory . status !== 0 ) {
172+ return fail (
173+ 'git-command-failed' ,
174+ `Failed to resolve the Git hooks path: ${ hooksDirectory . stderr . trim ( ) } ` ,
175+ ) ;
176+ }
177+
178+ const resolvedDirectory = removeLineEnding ( hooksDirectory . stdout ) ;
179+ if ( ! resolvedDirectory ) {
180+ return fail ( 'git-command-failed' , 'Failed to resolve the Git hooks path.' ) ;
181+ }
182+ return resolvedDirectory ;
183+ } ;
184+
114185const resolveGitContext = ( cwd : string ) : GitContext | InstallResult => {
115186 // Resolve every repository path in one Git process. `--git-path hooks`
116- // accounts for an existing local or global core.hooksPath configuration.
187+ // accounts for the effective core.hooksPath configuration across Git scopes .
117188 const repository = runGit ( cwd , [
118189 'rev-parse' ,
119190 '--is-inside-work-tree' ,
@@ -330,6 +401,12 @@ export const installHooks = ({
330401 }
331402 }
332403
404+ // Preserve a worktree-scoped override instead of writing a shadowed local value.
405+ const configScope = hooksPathMatches ? '--local' : resolveHooksPathScope ( cwd ) ;
406+ if ( typeof configScope !== 'string' ) {
407+ return configScope ;
408+ }
409+
333410 const files = Object . entries ( createHookFiles ( ) ) ;
334411 try {
335412 mkdirSync ( directory , { recursive : true } ) ;
@@ -370,7 +447,7 @@ export const installHooks = ({
370447 // Point Git at the generated directory only after every runtime file is ready.
371448 const configured = runGit ( cwd , [
372449 'config' ,
373- '--local' ,
450+ configScope ,
374451 'core.hooksPath' ,
375452 hooksPath ,
376453 ] ) ;
@@ -384,6 +461,17 @@ export const installHooks = ({
384461 ) ;
385462 }
386463
464+ const configuredHooksPath = resolveGitHooksPath ( cwd ) ;
465+ if ( typeof configuredHooksPath !== 'string' ) {
466+ return configuredHooksPath ;
467+ }
468+ if ( ! isSamePath ( configuredHooksPath , directory ) ) {
469+ return fail (
470+ 'git-config-failed' ,
471+ `Failed to activate Rstack Git hooks: core.hooksPath resolves to "${ displayPath ( gitRoot , configuredHooksPath ) } " instead of "${ hooksPath } ".` ,
472+ ) ;
473+ }
474+
387475 return {
388476 status : 'installed' ,
389477 hooksPath,
0 commit comments