@@ -89,8 +89,40 @@ function run(cmd, args, options = {}) {
8989 return result ;
9090}
9191
92+ let cachedGitExecutable = undefined ;
93+ let cachedGitAwareEnv = undefined ;
94+
95+ function resolveGitExecutable ( ) {
96+ if ( cachedGitExecutable !== undefined ) {
97+ return cachedGitExecutable ;
98+ }
99+
100+ if ( process . platform !== "win32" ) {
101+ const resolved = spawnSync ( "git" , [ "--exec-path" ] , {
102+ cwd : REPO_ROOT ,
103+ encoding : "utf-8" ,
104+ env : process . env ,
105+ } ) ;
106+ cachedGitExecutable = resolved . status === 0 ? "git" : "git" ;
107+ return cachedGitExecutable ;
108+ }
109+
110+ cachedGitExecutable = findPreferredGitExecutable ( ) || "git" ;
111+ return cachedGitExecutable ;
112+ }
113+
92114function git ( args , options = { } ) {
93- return run ( "git" , args , options ) ;
115+ return run ( resolveGitExecutable ( ) , args , {
116+ ...options ,
117+ env : buildGitAwareEnv ( options . env ) ,
118+ } ) ;
119+ }
120+
121+ function gh ( args , options = { } ) {
122+ return run ( "gh" , args , {
123+ ...options ,
124+ env : buildGitAwareEnv ( options . env ) ,
125+ } ) ;
94126}
95127
96128function gitOut ( args ) {
@@ -181,49 +213,117 @@ function metadataRel(packageDir) {
181213 return `${ packageRelDir ( packageDir ) } /API.metadata.yml` ;
182214}
183215
184- function findRealGitExe ( ) {
216+ function apiReviewBranchName ( kind , packageName , version ) {
217+ return `apireview/${ kind } _${ packageName } _${ version } ` ;
218+ }
219+
220+ function scoreGitCandidate ( candidate ) {
221+ const normalized = candidate . replace ( / \/ / g, "\\" ) . toLowerCase ( ) ;
222+ if ( normalized . includes ( "\\program files\\git\\cmd\\git.exe" ) ) {
223+ return 0 ;
224+ }
225+
226+ if ( normalized . includes ( "\\program files\\git\\bin\\git.exe" ) ) {
227+ return 1 ;
228+ }
229+
230+ if ( normalized . includes ( "\\git\\cmd\\git.exe" ) ) {
231+ return 2 ;
232+ }
233+
234+ if ( normalized . includes ( "\\git\\bin\\git.exe" ) ) {
235+ return 3 ;
236+ }
237+
238+ if ( normalized . includes ( "\\windows\\" ) ) {
239+ return 100 ;
240+ }
241+
242+ return 10 ;
243+ }
244+
245+ function findPreferredGitExecutable ( ) {
185246 if ( process . platform !== "win32" ) {
186247 return null ;
187248 }
188249
189- const seen = new Set ( ) ;
190- const entries = ( process . env . PATH || "" ) . split ( path . delimiter ) ;
191- for ( const rawEntry of entries ) {
192- const entry = rawEntry . replace ( / ^ " | " $ / g, "" ) ;
193- if ( ! entry ) {
250+ const candidates = new Set ( ) ;
251+ const roots = [ process . env . ProgramW6432 , process . env . ProgramFiles , process . env [ "ProgramFiles(x86)" ] , process . env . LocalAppData ] ;
252+ for ( const root of roots ) {
253+ if ( ! root ) {
194254 continue ;
195255 }
196256
197- const key = entry . toLowerCase ( ) ;
198- if ( seen . has ( key ) ) {
257+ candidates . add ( path . join ( root , "Git" , "cmd" , "git.exe" ) ) ;
258+ candidates . add ( path . join ( root , "Git" , "bin" , "git.exe" ) ) ;
259+ candidates . add ( path . join ( root , "Programs" , "Git" , "cmd" , "git.exe" ) ) ;
260+ candidates . add ( path . join ( root , "Programs" , "Git" , "bin" , "git.exe" ) ) ;
261+ }
262+
263+ for ( const rawEntry of ( process . env . PATH || "" ) . split ( path . delimiter ) ) {
264+ const entry = rawEntry . replace ( / ^ " | " $ / g, "" ) ;
265+ if ( ! entry ) {
199266 continue ;
200267 }
201- seen . add ( key ) ;
202268
203- const candidate = path . join ( entry , "git.exe" ) ;
204- if ( fs . existsSync ( candidate ) ) {
205- return candidate ;
206- }
269+ candidates . add ( path . join ( entry , "git.exe" ) ) ;
207270 }
208271
209- const fallback = "C:\\Program Files\\Git\\cmd\\git.exe" ;
210- return fs . existsSync ( fallback ) ? fallback : null ;
272+ const existing = [ ...candidates ] . filter ( ( candidate ) => fs . existsSync ( candidate ) ) ;
273+ existing . sort ( ( left , right ) => scoreGitCandidate ( left ) - scoreGitCandidate ( right ) || left . localeCompare ( right ) ) ;
274+ return existing [ 0 ] || null ;
211275}
212276
213- function envWithRealGit ( ) {
214- const env = { ...process . env } ;
215- const realGit = findRealGitExe ( ) ;
216- if ( ! realGit ) {
217- return env ;
277+ function getGitExecPath ( gitExecutable ) {
278+ if ( process . platform === "win32" && ! path . isAbsolute ( gitExecutable ) ) {
279+ return null ;
280+ }
281+
282+ const result = spawnSync ( gitExecutable , [ "--exec-path" ] , {
283+ cwd : REPO_ROOT ,
284+ encoding : "utf-8" ,
285+ } ) ;
286+
287+ if ( result . status !== 0 ) {
288+ return null ;
289+ }
290+
291+ return result . stdout . trim ( ) || null ;
292+ }
293+
294+ function samePathEntry ( left , right ) {
295+ if ( process . platform === "win32" ) {
296+ return left . replace ( / \\ + $ / , "" ) . toLowerCase ( ) === right . replace ( / \\ + $ / , "" ) . toLowerCase ( ) ;
297+ }
298+
299+ return left === right ;
300+ }
301+
302+ function buildGitAwareEnv ( baseEnv = process . env ) {
303+ if ( baseEnv === process . env && cachedGitAwareEnv ) {
304+ return cachedGitAwareEnv ;
305+ }
306+
307+ const env = { ...baseEnv } ;
308+ const gitExecutable = resolveGitExecutable ( ) ;
309+ const gitExecPath = getGitExecPath ( gitExecutable ) ;
310+
311+ if ( path . isAbsolute ( gitExecutable ) ) {
312+ const gitDir = path . dirname ( gitExecutable ) ;
313+ const currentEntries = ( env . PATH || "" ) . split ( path . delimiter ) . filter ( Boolean ) ;
314+ const first = currentEntries [ 0 ] || "" ;
315+ if ( ! first || ! samePathEntry ( first , gitDir ) ) {
316+ env . PATH = [ gitDir , ...currentEntries ] . join ( path . delimiter ) ;
317+ logInfo ( `(using resolved git executable: ${ gitExecutable } )` ) ;
318+ }
319+ }
320+
321+ if ( gitExecPath ) {
322+ env . GIT_EXEC_PATH = gitExecPath ;
218323 }
219324
220- const gitDir = path . dirname ( realGit ) ;
221- const current = env . PATH || "" ;
222- const parts = current . split ( path . delimiter ) ;
223- const first = parts [ 0 ] || "" ;
224- if ( first . replace ( / \\ + $ / , "" ) . toLowerCase ( ) !== gitDir . replace ( / \\ + $ / , "" ) . toLowerCase ( ) ) {
225- env . PATH = `${ gitDir } ${ path . delimiter } ${ current } ` ;
226- logInfo ( `(prepending real git to PATH for gh: ${ gitDir } )` ) ;
325+ if ( baseEnv === process . env ) {
326+ cachedGitAwareEnv = env ;
227327 }
228328
229329 return env ;
@@ -253,7 +353,6 @@ function selectBestPr(prs) {
253353}
254354
255355function findOpenPrForHead ( headSelector ) {
256- const env = envWithRealGit ( ) ;
257356 const selectors = [ headSelector ] ;
258357 if ( headSelector . includes ( ":" ) ) {
259358 const branchOnly = headSelector . split ( ":" , 2 ) [ 1 ] ;
@@ -264,8 +363,7 @@ function findOpenPrForHead(headSelector) {
264363
265364 const allPrs = [ ] ;
266365 for ( const selector of selectors ) {
267- const direct = run (
268- "gh" ,
366+ const direct = gh (
269367 [
270368 "pr" ,
271369 "list" ,
@@ -280,7 +378,7 @@ function findOpenPrForHead(headSelector) {
280378 "--limit" ,
281379 "50" ,
282380 ] ,
283- { check : false , capture : true , env } ,
381+ { check : false , capture : true } ,
284382 ) ;
285383
286384 if ( direct . status === 0 ) {
@@ -293,8 +391,7 @@ function findOpenPrForHead(headSelector) {
293391
294392 for ( const selector of selectors ) {
295393 const searchQuery = `repo:Azure/azure-sdk-for-python head:${ selector } ` ;
296- const search = run (
297- "gh" ,
394+ const search = gh (
298395 [
299396 "pr" ,
300397 "list" ,
@@ -309,7 +406,7 @@ function findOpenPrForHead(headSelector) {
309406 "--limit" ,
310407 "50" ,
311408 ] ,
312- { check : false , capture : true , env } ,
409+ { check : false , capture : true } ,
313410 ) ;
314411
315412 if ( search . status === 0 ) {
@@ -399,7 +496,7 @@ function generateApiBytesForRef({
399496 // Restore the package directory to the current branch state
400497 git ( [ "checkout" , "HEAD" , "--" , packageRelative ] ) ;
401498 // Clean any untracked files that the generation may have left behind
402- run ( " git" , [ "clean" , "-fd" , "--" , packageRelative ] , { check : false } ) ;
499+ git ( [ "clean" , "-fd" , "--" , packageRelative ] , { check : false } ) ;
403500 }
404501}
405502
@@ -454,8 +551,8 @@ function main() {
454551 } ) ;
455552 const targetVersion = targetResult . version ;
456553
457- const baseBranch = `base_ ${ args . packageName } _ ${ baseVersion } ` ;
458- const reviewBranch = `review_ ${ args . packageName } _ ${ targetVersion } ` ;
554+ const baseBranch = apiReviewBranchName ( "base" , args . packageName , baseVersion ) ;
555+ const reviewBranch = apiReviewBranchName ( "review" , args . packageName , targetVersion ) ;
459556
460557 logInfo ( `\n=== Creating base branch ${ baseBranch } ===` ) ;
461558 git ( [ "checkout" , "-B" , baseBranch , MAIN_REF ] ) ;
@@ -548,8 +645,7 @@ function main() {
548645
549646 logInfo ( "\n=== Opening PR ===" ) ;
550647 const compareUrl = `https://github.com/Azure/azure-sdk-for-python/compare/${ baseBranch } ...${ reviewBranch } ?expand=1` ;
551- const prCreate = run (
552- "gh" ,
648+ const prCreate = gh (
553649 [
554650 "pr" ,
555651 "create" ,
@@ -565,7 +661,7 @@ function main() {
565661 body ,
566662 "--draft" ,
567663 ] ,
568- { check : false , env : envWithRealGit ( ) } ,
664+ { check : false } ,
569665 ) ;
570666
571667 if ( prCreate . status !== 0 ) {
0 commit comments