@@ -73,7 +73,7 @@ export async function getRemoteUrl(dir: string, remoteName: string): Promise<str
7373 if ( result . exitCode === 0 && result . stdout . trim ( ) . length > 0 ) {
7474 return result . stdout . trim ( ) ;
7575 }
76-
76+
7777 // Fallback: try to get the first remote if the specified one doesn't exist
7878 const remotesResult = toGitStringResult ( await exec ( [ 'remote' ] , dir ) ) ;
7979 if ( remotesResult . exitCode === 0 ) {
@@ -86,7 +86,7 @@ export async function getRemoteUrl(dir: string, remoteName: string): Promise<str
8686 }
8787 }
8888 }
89-
89+
9090 return '' ;
9191}
9292
@@ -343,3 +343,102 @@ export async function getRemoteName(dir: string, branch: string): Promise<string
343343 }
344344 return 'origin' ;
345345}
346+
347+ /**
348+ * Check if there are stale git lock files and optionally remove them.
349+ * Lock files can be left behind if git operations are interrupted.
350+ *
351+ * @param dir The git repository directory
352+ * @param logger Optional logger for debugging
353+ * @returns Array of lock file paths that were found
354+ */
355+ export async function checkGitLockFiles ( dir : string , logger ?: ILogger ) : Promise < string [ ] > {
356+ const logDebug = ( message : string ) : unknown =>
357+ logger ?. debug ( message , {
358+ functionName : 'checkGitLockFiles' ,
359+ step : GitStep . CheckingLocalGitRepoSanity ,
360+ dir,
361+ } ) ;
362+
363+ try {
364+ const gitDir = await getGitDirectory ( dir ) ;
365+ const lockFiles = [
366+ path . join ( gitDir , 'index.lock' ) ,
367+ path . join ( gitDir , 'HEAD.lock' ) ,
368+ path . join ( gitDir , 'refs' , 'heads' , '*.lock' ) ,
369+ path . join ( gitDir , 'refs' , 'remotes' , '*.lock' ) ,
370+ ] ;
371+
372+ const foundLockFiles : string [ ] = [ ] ;
373+
374+ for ( const lockPattern of lockFiles ) {
375+ if ( lockPattern . includes ( '*' ) ) {
376+ // Handle wildcard patterns - check parent directory
377+ const parentDir = path . dirname ( lockPattern ) ;
378+ if ( await fs . pathExists ( parentDir ) ) {
379+ const files = await fs . readdir ( parentDir ) ;
380+ for ( const file of files ) {
381+ if ( file . endsWith ( '.lock' ) ) {
382+ const fullPath = path . join ( parentDir , file ) ;
383+ foundLockFiles . push ( fullPath ) ;
384+ logDebug ( `Found lock file: ${ fullPath } ` ) ;
385+ }
386+ }
387+ }
388+ } else {
389+ // Direct path check
390+ if ( await fs . pathExists ( lockPattern ) ) {
391+ foundLockFiles . push ( lockPattern ) ;
392+ logDebug ( `Found lock file: ${ lockPattern } ` ) ;
393+ }
394+ }
395+ }
396+
397+ return foundLockFiles ;
398+ } catch ( error ) {
399+ logDebug ( `Error checking lock files: ${ ( error as Error ) . message } ` ) ;
400+ return [ ] ;
401+ }
402+ }
403+
404+ /**
405+ * Remove stale git lock files that may block git operations.
406+ * This should only be called when you're sure no other git operations are running.
407+ *
408+ * @param dir The git repository directory
409+ * @param logger Optional logger for debugging
410+ * @returns Number of lock files removed
411+ */
412+ export async function removeGitLockFiles ( dir : string , logger ?: ILogger ) : Promise < number > {
413+ const logDebug = ( message : string ) : unknown =>
414+ logger ?. debug ( message , {
415+ functionName : 'removeGitLockFiles' ,
416+ step : GitStep . CheckingLocalGitRepoSanity ,
417+ dir,
418+ } ) ;
419+ const logWarn = ( message : string ) : unknown =>
420+ logger ?. warn ?.( message , {
421+ functionName : 'removeGitLockFiles' ,
422+ step : GitStep . CheckingLocalGitRepoSanity ,
423+ dir,
424+ } ) ;
425+
426+ const lockFiles = await checkGitLockFiles ( dir , logger ) ;
427+ let removedCount = 0 ;
428+
429+ for ( const lockFile of lockFiles ) {
430+ try {
431+ await fs . remove ( lockFile ) ;
432+ removedCount ++ ;
433+ logWarn ( `Removed stale lock file: ${ lockFile } ` ) ;
434+ } catch ( error ) {
435+ logDebug ( `Failed to remove lock file ${ lockFile } : ${ ( error as Error ) . message } ` ) ;
436+ }
437+ }
438+
439+ if ( removedCount > 0 ) {
440+ logWarn ( `Removed ${ removedCount } stale git lock file(s)` ) ;
441+ }
442+
443+ return removedCount ;
444+ }
0 commit comments