@@ -347,6 +347,10 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
347347
348348 branch = branchMap . get ( tip ) ;
349349 branchId = branch ?. id ?? getBranchId ( repoPath , false , tip ) ;
350+
351+ // Check if branch has commits that can be recomposed
352+ const recomposable = await this . isBranchRecomposable ( branch , repoPath ) ;
353+
350354 context = {
351355 webviewItem : `gitlens:branch${ head ? '+current' : '' } ${
352356 branch ?. upstream != null ? '+tracking' : ''
@@ -358,7 +362,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
358362 : ''
359363 } ${ branch ?. starred ? '+starred' : '' } ${ branch ?. upstream ?. state . ahead ? '+ahead' : '' } ${
360364 branch ?. upstream ?. state . behind ? '+behind' : ''
361- } `,
365+ } ${ recomposable ? '+recomposable' : '' } `,
362366 webviewItemValue : {
363367 type : 'branch' ,
364368 ref : createReference ( tip , repoPath , {
@@ -617,6 +621,59 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
617621 return getCommitsForGraphCore . call ( this , defaultLimit , selectSha , undefined , cancellation ) ;
618622 }
619623
624+ private async isBranchRecomposable ( branch : GitBranch | undefined , repoPath : string ) : Promise < boolean > {
625+ if ( ! branch || branch . remote ) return false ;
626+
627+ try {
628+ const upstreamName = branch . upstream ?. name ;
629+ const svc = this . container . git . getRepositoryService ( repoPath ) ;
630+
631+ // Get stored merge target configurations
632+ const [ storedTargetResult , storedMergeBaseResult ] = await Promise . allSettled ( [
633+ svc . branches . getStoredMergeTargetBranchName ?.( branch . name ) ,
634+ svc . branches . getBaseBranchName ?.( branch . name ) ,
635+ ] ) ;
636+ const storedTarget = getSettledValue ( storedTargetResult ) ;
637+ const validStoredTarget = storedTarget && storedTarget !== upstreamName ? storedTarget : undefined ;
638+ const storedMergeBase = getSettledValue ( storedMergeBaseResult ) ;
639+ const validStoredMergeBase =
640+ storedMergeBase && storedMergeBase !== upstreamName ? storedMergeBase : undefined ;
641+
642+ // Select target with most recent common commit (closest to branch tip)
643+ const validTargets = [ validStoredTarget , validStoredMergeBase ] ;
644+ const targetCommit = await this . selectMostRecentMergeBase ( branch . name , validTargets , svc ) ;
645+
646+ return Boolean ( targetCommit && targetCommit !== branch . sha ) ;
647+ } catch {
648+ // If we can't determine, assume not recomposable
649+ return false ;
650+ }
651+ }
652+
653+ private async selectMostRecentMergeBase (
654+ branchName : string ,
655+ targets : ( string | undefined ) [ ] ,
656+ svc : ReturnType < typeof this . container . git . getRepositoryService > ,
657+ ) : Promise < string | undefined > {
658+ const mergeBaseResults = await Promise . allSettled (
659+ targets . map ( target => target && svc . refs . getMergeBase ( branchName , target ) ) ,
660+ ) ;
661+ const isString = ( t : string | undefined ) : t is string => Boolean ( t ) ;
662+ const mergeBases = mergeBaseResults . map ( result => getSettledValue ( result ) ) . filter ( isString ) ;
663+
664+ if ( mergeBases . length === 0 ) return undefined ;
665+
666+ let mostRecentMergeBase = mergeBases [ 0 ] ;
667+ for ( let i = 1 ; i < mergeBases . length ; i ++ ) {
668+ const isCurrentMoreRecent = await svc . commits . isAncestorOf ( mostRecentMergeBase , mergeBases [ i ] ) ;
669+ if ( isCurrentMoreRecent ) {
670+ mostRecentMergeBase = mergeBases [ i ] ;
671+ }
672+ }
673+
674+ return mostRecentMergeBase ;
675+ }
676+
620677 @log < GraphGitSubProvider [ 'searchGraph' ] > ( {
621678 args : {
622679 1 : s =>
0 commit comments