@@ -96,33 +96,56 @@ async function detectMainBranch(repoRoot: string): Promise<string> {
9696 return result ;
9797}
9898
99- async function detectMainBranchUncached ( repoRoot : string ) : Promise < string > {
100- // Try remote HEAD reference first
99+ /** Read the branch name that refs/remotes/origin/HEAD points to, or null. */
100+ async function resolveOriginHead ( repoRoot : string ) : Promise < string | null > {
101+ const prefix = 'refs/remotes/origin/' ;
101102 try {
102103 const { stdout } = await exec ( 'git' , [ 'symbolic-ref' , 'refs/remotes/origin/HEAD' ] , {
103104 cwd : repoRoot ,
104105 } ) ;
105106 const refname = stdout . trim ( ) ;
106- const prefix = 'refs/remotes/origin/' ;
107- if ( refname . startsWith ( prefix ) ) return refname . slice ( prefix . length ) ;
107+ return refname . startsWith ( prefix ) ? refname . slice ( prefix . length ) : null ;
108108 } catch {
109- /* ignore */
109+ return null ;
110110 }
111+ }
111112
112- // Check if 'main' exists
113+ /** Check whether the remote-tracking ref origin/<branch> exists locally. */
114+ async function remoteTrackingRefExists ( repoRoot : string , branch : string ) : Promise < boolean > {
113115 try {
114- await exec ( 'git' , [ 'rev-parse' , '--verify' , 'main' ] , { cwd : repoRoot } ) ;
115- return 'main' ;
116+ await exec ( 'git' , [ 'rev-parse' , '--verify' , `refs/remotes/origin/${ branch } ` ] , {
117+ cwd : repoRoot ,
118+ } ) ;
119+ return true ;
116120 } catch {
117- /* ignore */
121+ return false ;
118122 }
123+ }
119124
120- // Fallback to 'master'
121- try {
122- await exec ( 'git' , [ 'rev-parse' , '--verify' , 'master' ] , { cwd : repoRoot } ) ;
123- return 'master' ;
124- } catch {
125- /* ignore */
125+ async function detectMainBranchUncached ( repoRoot : string ) : Promise < string > {
126+ // Try remote HEAD reference first
127+ const branch = await resolveOriginHead ( repoRoot ) ;
128+ if ( branch ) {
129+ // Verify the remote-tracking ref exists — refs/remotes/origin/HEAD can go
130+ // stale when the default branch is changed on the remote.
131+ if ( await remoteTrackingRefExists ( repoRoot , branch ) ) return branch ;
132+
133+ // Stale ref — try refreshing from the remote
134+ try {
135+ await exec ( 'git' , [ 'remote' , 'set-head' , 'origin' , '--auto' ] , {
136+ cwd : repoRoot ,
137+ timeout : 5_000 ,
138+ } ) ;
139+ const refreshed = await resolveOriginHead ( repoRoot ) ;
140+ if ( refreshed && ( await remoteTrackingRefExists ( repoRoot , refreshed ) ) ) return refreshed ;
141+ } catch {
142+ /* no network or no remote — fall through */
143+ }
144+ }
145+
146+ // Check common default branch names
147+ for ( const candidate of [ 'main' , 'master' ] ) {
148+ if ( await remoteTrackingRefExists ( repoRoot , candidate ) ) return candidate ;
126149 }
127150
128151 // Empty repo (no commits yet) — use configured default branch or fall back to "main"
0 commit comments