@@ -5,12 +5,14 @@ import { GitErrorHandling } from '../../../../git/commandOptions';
55import  type  { 
66	BranchContributionsOverview , 
77	GitBranchesSubProvider , 
8+ 	GitBranchMergedStatus , 
89	PagedResult , 
910	PagingOptions , 
1011}  from  '../../../../git/gitProvider' ; 
1112import  {  GitBranch  }  from  '../../../../git/models/branch' ; 
1213import  {  getLocalBranchByUpstream ,  isDetachedHead  }  from  '../../../../git/models/branch.utils' ; 
1314import  type  {  MergeConflict  }  from  '../../../../git/models/mergeConflict' ; 
15+ import  type  {  GitBranchReference  }  from  '../../../../git/models/reference' ; 
1416import  {  createRevisionRange  }  from  '../../../../git/models/revision.utils' ; 
1517import  {  parseGitBranches  }  from  '../../../../git/parsers/branchParser' ; 
1618import  {  parseMergeTreeConflict  }  from  '../../../../git/parsers/mergeTreeParser' ; 
@@ -310,6 +312,74 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
310312		await  this . git . branch ( repoPath ,  name ,  ref ) ; 
311313	} 
312314
315+ 	@log ( ) 
316+ 	async  getBranchMergedStatus ( 
317+ 		repoPath : string , 
318+ 		branch : GitBranchReference , 
319+ 		into : GitBranchReference , 
320+ 	) : Promise < GitBranchMergedStatus >  { 
321+ 		const  result  =  await  this . getBranchMergedStatusCore ( repoPath ,  branch ,  into ) ; 
322+ 		if  ( result . merged )  return  result ; 
323+ 
324+ 		// If the branch we are checking is a remote branch, check if it has been merged into its local branch (if there is one) 
325+ 		if  ( into . remote )  { 
326+ 			const  localIntoBranch  =  await  this . getLocalBranchByUpstream ( repoPath ,  into . name ) ; 
327+ 			// If there is a local branch and it is not the branch we are checking, check if it has been merged into it 
328+ 			if  ( localIntoBranch  !=  null  &&  localIntoBranch . name  !==  branch . name )  { 
329+ 				const  result  =  await  this . getBranchMergedStatusCore ( repoPath ,  branch ,  localIntoBranch ) ; 
330+ 				if  ( result . merged )  { 
331+ 					return  { 
332+ 						...result , 
333+ 						localBranchOnly : {  name : localIntoBranch . name  } , 
334+ 					} ; 
335+ 				} 
336+ 			} 
337+ 		} 
338+ 
339+ 		return  {  merged : false  } ; 
340+ 	} 
341+ 
342+ 	private  async  getBranchMergedStatusCore ( 
343+ 		repoPath : string , 
344+ 		branch : GitBranchReference , 
345+ 		into : GitBranchReference , 
346+ 	) : Promise < Exclude < GitBranchMergedStatus ,  'localBranchOnly' > >  { 
347+ 		const  scope  =  getLogScope ( ) ; 
348+ 
349+ 		try  { 
350+ 			// Check if branch is direct ancestor (handles FF merges) 
351+ 			try  { 
352+ 				await  this . git . exec ( 
353+ 					{  cwd : repoPath ,  errors : GitErrorHandling . Throw  } , 
354+ 					'merge-base' , 
355+ 					'--is-ancestor' , 
356+ 					branch . name , 
357+ 					into . name , 
358+ 				) ; 
359+ 				return  {  merged : true ,  confidence : 'highest'  } ; 
360+ 			}  catch  { } 
361+ 
362+ 			// Cherry-pick detection (handles cherry-picks, rebases, etc) 
363+ 			const  data  =  await  this . git . exec < string > ( 
364+ 				{  cwd : repoPath  } , 
365+ 				'cherry' , 
366+ 				'--abbrev' , 
367+ 				'-v' , 
368+ 				into . name , 
369+ 				branch . name , 
370+ 			) ; 
371+ 			// Check if there are no lines or all lines startwith a `-` (i.e. likely merged) 
372+ 			if  ( ! data  ||  data . split ( '\n' ) . every ( l  =>  l . startsWith ( '-' ) ) )  { 
373+ 				return  {  merged : true ,  confidence : 'high'  } ; 
374+ 			} 
375+ 
376+ 			return  {  merged : false  } ; 
377+ 		}  catch  ( ex )  { 
378+ 			Logger . error ( ex ,  scope ) ; 
379+ 			return  {  merged : false  } ; 
380+ 		} 
381+ 	} 
382+ 
313383	@log ( ) 
314384	async  getLocalBranchByUpstream ( repoPath : string ,  remoteBranchName : string ) : Promise < GitBranch  |  undefined >  { 
315385		const  branches  =  new  PageableResult < GitBranch > ( p  => 
@@ -425,7 +495,12 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
425495			if  ( match  !=  null  &&  match . length  ===  2 )  { 
426496				let  name : string  |  undefined  =  match [ 1 ] ; 
427497				if  ( name  !==  'HEAD' )  { 
428- 					name  =  await  this . getValidatedBranchName ( repoPath ,  options ?. upstream  ? `${ name }  @{u}`  : name ) ; 
498+ 					if  ( options ?. upstream )  { 
499+ 						const  upstream  =  await  this . getValidatedBranchName ( repoPath ,  `${ name }  @{u}` ) ; 
500+ 						if  ( upstream )  return  upstream ; 
501+ 					} 
502+ 
503+ 					name  =  await  this . getValidatedBranchName ( repoPath ,  name ) ; 
429504					if  ( name )  return  name ; 
430505				} 
431506			} 
@@ -438,13 +513,17 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
438513				`--grep-reflog=checkout: moving from .* to ${ ref . replace ( 'refs/heads/' ,  '' ) }  ` , 
439514			) ; 
440515			entries  =  data . split ( '\n' ) . filter ( entry  =>  Boolean ( entry ) ) ; 
441- 
442516			if  ( ! entries . length )  return  undefined ; 
443517
444518			match  =  entries [ entries . length  -  1 ] . match ( / c h e c k o u t :   m o v i n g   f r o m   ( [ ^ \s ] + ) \s / ) ; 
445519			if  ( match  !=  null  &&  match . length  ===  2 )  { 
446520				let  name : string  |  undefined  =  match [ 1 ] ; 
447- 				name  =  await  this . getValidatedBranchName ( repoPath ,  options ?. upstream  ? `${ name }  @{u}`  : name ) ; 
521+ 				if  ( options ?. upstream )  { 
522+ 					const  upstream  =  await  this . getValidatedBranchName ( repoPath ,  `${ name }  @{u}` ) ; 
523+ 					if  ( upstream )  return  upstream ; 
524+ 				} 
525+ 
526+ 				name  =  await  this . getValidatedBranchName ( repoPath ,  name ) ; 
448527				if  ( name )  return  name ; 
449528			} 
450529		}  catch  { } 
0 commit comments