@@ -7,6 +7,7 @@ import type { GitCache } from '../../../../git/cache';
77import type { GitCommandOptions } from '../../../../git/commandOptions' ;
88import { GitErrorHandling } from '../../../../git/commandOptions' ;
99import type {
10+ GitCommitReachability ,
1011 GitCommitsSubProvider ,
1112 GitLogForPathOptions ,
1213 GitLogOptions ,
@@ -53,6 +54,9 @@ import { filterMap, first, join, last, some } from '../../../../system/iterable'
5354import { Logger } from '../../../../system/logger' ;
5455import { getLogScope } from '../../../../system/logger.scope' ;
5556import { isFolderGlob , stripFolderGlob } from '../../../../system/path' ;
57+ import { wait } from '../../../../system/promise' ;
58+ import type { Cancellable } from '../../../../system/promiseCache' ;
59+ import { PromiseCache } from '../../../../system/promiseCache' ;
5660import { maybeStopWatch } from '../../../../system/stopwatch' ;
5761import type { CachedLog , TrackedGitDocument } from '../../../../trackers/trackedDocument' ;
5862import { GitDocumentState } from '../../../../trackers/trackedDocument' ;
@@ -174,6 +178,104 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
174178 }
175179 }
176180
181+ @log ( )
182+ async getCommitReachability (
183+ repoPath : string ,
184+ rev : string ,
185+ cancellation ?: CancellationToken ,
186+ ) : Promise < GitCommitReachability | undefined > {
187+ if ( repoPath == null || isUncommitted ( rev ) ) return undefined ;
188+
189+ const scope = getLogScope ( ) ;
190+
191+ const getCore = async ( cancellable ?: Cancellable ) => {
192+ try {
193+ // Use for-each-ref with %(HEAD) to mark current branch with *
194+ const result = await this . git . exec (
195+ { cwd : repoPath , cancellation : cancellation , errors : GitErrorHandling . Ignore } ,
196+ 'for-each-ref' ,
197+ '--contains' ,
198+ rev ,
199+ '--format=%(HEAD)%(refname)' ,
200+ '--sort=-version:refname' ,
201+ '--sort=-committerdate' ,
202+ '--sort=-HEAD' ,
203+ 'refs/heads/' ,
204+ 'refs/remotes/' ,
205+ 'refs/tags/' ,
206+ ) ;
207+ if ( cancellation ?. isCancellationRequested ) throw new CancellationError ( ) ;
208+
209+ const refs : GitCommitReachability [ 'refs' ] = [ ] ;
210+
211+ // Parse branches from refs/heads/ and refs/remotes/
212+ if ( result ?. stdout ) {
213+ const lines = result . stdout . split ( '\n' ) ;
214+
215+ for ( let line of lines ) {
216+ line = line . trim ( ) ;
217+ if ( ! line ) continue ;
218+
219+ // %(HEAD) outputs '*' for current branch, ' ' for others
220+ const isCurrent = line . startsWith ( '*' ) ;
221+ const refname = isCurrent ? line . substring ( 1 ) : line ; // Skip the HEAD marker
222+
223+ // Skip HEADs
224+ if ( refname . endsWith ( '/HEAD' ) ) continue ;
225+
226+ if ( refname . startsWith ( 'refs/heads/' ) ) {
227+ // Remove 'refs/heads/'
228+ const name = refname . substring ( 11 ) ;
229+ refs . push ( {
230+ refType : 'branch' ,
231+ name : name ,
232+ remote : false ,
233+ current : isCurrent ,
234+ } ) ;
235+ } else if ( refname . startsWith ( 'refs/remotes/' ) ) {
236+ // Remove 'refs/remotes/'
237+ refs . push ( { refType : 'branch' , name : refname . substring ( 13 ) , remote : true } ) ;
238+ } else if ( refname . startsWith ( 'refs/tags/' ) ) {
239+ // Remove 'refs/tags/'
240+ refs . push ( { refType : 'tag' , name : refname . substring ( 10 ) } ) ;
241+ }
242+ }
243+ }
244+
245+ // Sort to move tags to the end, preserving order within each type
246+ refs . sort ( ( a , b ) => ( a . refType !== b . refType ? ( a . refType === 'tag' ? 1 : - 1 ) : 0 ) ) ;
247+
248+ await wait ( 20000 ) ;
249+
250+ return { refs : refs } ;
251+ } catch ( ex ) {
252+ cancellable ?. cancelled ( ) ;
253+ debugger ;
254+ if ( isCancellationError ( ex ) ) throw ex ;
255+
256+ Logger . error ( ex , scope ) ;
257+
258+ return undefined ;
259+ }
260+ } ;
261+
262+ const cache = this . cache . reachability ;
263+ if ( cache == null ) return getCore ( ) ;
264+
265+ let reachabilityCache = cache . get ( repoPath ) ;
266+ if ( reachabilityCache == null ) {
267+ cache . set (
268+ repoPath ,
269+ ( reachabilityCache = new PromiseCache < string , GitCommitReachability | undefined > ( {
270+ accessTTL : 1000 * 60 * 60 , // 60 minutes
271+ capacity : 25 , // Limit to 25 commits per repo
272+ } ) ) ,
273+ ) ;
274+ }
275+
276+ return reachabilityCache . get ( rev , getCore ) ;
277+ }
278+
177279 @log ( )
178280 async getIncomingActivity (
179281 repoPath : string ,
0 commit comments