@@ -11,10 +11,16 @@ const TRUNCATION_MARKER = "[truncated]";
1111const TEMPLATE_PATHS = [
1212 ".github/pull_request_template.md" ,
1313 ".github/PULL_REQUEST_TEMPLATE.md" ,
14+ ".github/pull_request_template.txt" ,
15+ ".github/PULL_REQUEST_TEMPLATE.txt" ,
1416 "pull_request_template.md" ,
1517 "PULL_REQUEST_TEMPLATE.md" ,
18+ "pull_request_template.txt" ,
19+ "PULL_REQUEST_TEMPLATE.txt" ,
1620 "docs/pull_request_template.md" ,
1721 "docs/PULL_REQUEST_TEMPLATE.md" ,
22+ "docs/pull_request_template.txt" ,
23+ "docs/PULL_REQUEST_TEMPLATE.txt" ,
1824] as const ;
1925
2026const TEMPLATE_DIRECTORIES = [
@@ -23,8 +29,6 @@ const TEMPLATE_DIRECTORIES = [
2329 "docs/PULL_REQUEST_TEMPLATE" ,
2430] as const ;
2531
26- const TREE_PATHS = [ ...TEMPLATE_PATHS , ...TEMPLATE_DIRECTORIES ] as const ;
27-
2832type ExecuteGit = SourceControlHost . SourceControlHost [ "Service" ] [ "git" ] [ "execute" ] ;
2933
3034interface TemplateTreeEntry {
@@ -105,7 +109,7 @@ const readTemplateDirectory = Effect.fn("gitHubChangeRequestTemplate.readTemplat
105109 return false ;
106110 }
107111 const relativePath = entry . path . slice ( prefix . length ) ;
108- return ! relativePath . includes ( "/" ) && relativePath . toLowerCase ( ) . endsWith ( ".md" ) ;
112+ return ! relativePath . includes ( "/" ) && / \. (?: m d | t x t ) $ / i . test ( relativePath ) ;
109113 } ) ;
110114
111115 const templates : string [ ] = [ ] ;
@@ -132,10 +136,50 @@ export const detect = Effect.fn("gitHubChangeRequestTemplate.detect")(function*
132136) {
133137 return yield * Effect . gen ( function * ( ) {
134138 // Read committed regular blobs; worktree paths can become symlinks between validation and open.
139+ // ls-tree does not support case-insensitive pathspecs; grep discovers only matching names.
140+ const paths = yield * executeGit ( {
141+ operation : "gitHubChangeRequestTemplate.findTemplatePaths" ,
142+ cwd,
143+ args : [
144+ "grep" ,
145+ "-G" ,
146+ "--no-color" ,
147+ "-l" ,
148+ "-z" ,
149+ "--full-name" ,
150+ "--no-recurse-submodules" ,
151+ "-e" ,
152+ "^" ,
153+ treeish ,
154+ "--" ,
155+ ...TEMPLATE_PATHS . map ( ( path ) => `:(top,icase,literal)${ path } ` ) ,
156+ ] ,
157+ maxOutputBytes : TREE_LIST_MAX_BYTES ,
158+ } ) . pipe (
159+ Effect . map ( ( result ) =>
160+ result . stdoutTruncated
161+ ? [ ]
162+ : result . stdout
163+ . split ( "\0" )
164+ . filter ( ( path ) => path . startsWith ( `${ treeish } :` ) )
165+ . map ( ( path ) => path . slice ( treeish . length + 1 ) ) ,
166+ ) ,
167+ // grep exits with status 1 when no nonempty templates match.
168+ Effect . orElseSucceed ( ( ) => [ ] ) ,
169+ ) ;
135170 const result = yield * executeGit ( {
136171 operation : "gitHubChangeRequestTemplate.listTemplates" ,
137172 cwd,
138- args : [ "ls-tree" , "-r" , "-z" , "--full-tree" , treeish , "--" , ...TREE_PATHS ] ,
173+ args : [
174+ "ls-tree" ,
175+ "-r" ,
176+ "-z" ,
177+ "--full-tree" ,
178+ treeish ,
179+ "--" ,
180+ ...new Set ( [ ...TEMPLATE_PATHS , ...paths ] ) ,
181+ ...TEMPLATE_DIRECTORIES ,
182+ ] ,
139183 maxOutputBytes : TREE_LIST_MAX_BYTES ,
140184 appendTruncationMarker : true ,
141185 } ) ;
@@ -144,12 +188,20 @@ export const detect = Effect.fn("gitHubChangeRequestTemplate.detect")(function*
144188 }
145189
146190 const entries = parseTemplateTreeEntries ( result . stdout ) ;
147- const entriesByPath = new Map ( entries . map ( ( entry ) => [ entry . path , entry ] ) ) ;
148- for ( const templatePath of TEMPLATE_PATHS ) {
149- const entry = entriesByPath . get ( templatePath ) ;
150- if ( ! entry ) {
151- continue ;
152- }
191+ const candidates = [ ".github/" , "" , "docs/" ] . flatMap ( ( location ) => {
192+ const paths = TEMPLATE_PATHS . filter (
193+ ( path ) => path . slice ( 0 , path . lastIndexOf ( "/" ) + 1 ) === location ,
194+ ) ;
195+ return [
196+ ...paths . flatMap ( ( path ) => entries . filter ( ( entry ) => entry . path === path ) ) ,
197+ ...entries . filter (
198+ ( entry ) =>
199+ ! paths . some ( ( path ) => path === entry . path ) &&
200+ paths . some ( ( path ) => path . toLowerCase ( ) === entry . path . toLowerCase ( ) ) ,
201+ ) ,
202+ ] ;
203+ } ) ;
204+ for ( const entry of candidates ) {
153205 const template = yield * readTemplateBlob ( { cwd, executeGit, entry } ) ;
154206 if ( Option . isSome ( template ) ) {
155207 return template ;
0 commit comments