@@ -54,7 +54,7 @@ import { EventType } from '../common/timelineEvent';
5454import { Schemes } from '../common/uri' ;
5555import { AsyncPredicate , batchPromiseAll , compareIgnoreCase , formatError , Predicate } from '../common/utils' ;
5656import { PULL_REQUEST_OVERVIEW_VIEW_TYPE } from '../common/webview' ;
57- import { LAST_USED_EMAIL , NEVER_SHOW_PULL_NOTIFICATION , REPO_KEYS , ReposState } from '../extensionState' ;
57+ import { BRANCHES_ASSOCIATED_WITH_PRS , LAST_USED_EMAIL , NEVER_SHOW_PULL_NOTIFICATION , REPO_KEYS , ReposState } from '../extensionState' ;
5858import { git } from '../gitProviders/gitCommands' ;
5959import { IThemeWatcher } from '../themeWatcher' ;
6060import { CreatePullRequestHelper } from '../view/createPullRequestHelper' ;
@@ -576,6 +576,11 @@ export class FolderRepositoryManager extends Disposable {
576576 this . getAssignableUsers ( repositoriesAdded . length > 0 ) ;
577577 if ( isAuthenticated && activeRemotes . length ) {
578578 this . state = ReposManagerState . RepositoriesLoaded ;
579+ // On first activation, associate local branches with PRs
580+ // Do this asynchronously to not block the main flow
581+ this . associateLocalBranchesWithPRsOnFirstActivation ( ) . catch ( e => {
582+ Logger . error ( `Failed to associate branches with PRs: ${ e } ` , this . id ) ;
583+ } ) ;
579584 } else if ( ! isAuthenticated ) {
580585 this . state = ReposManagerState . NeedsAuthentication ;
581586 }
@@ -953,6 +958,111 @@ export class FolderRepositoryManager extends Disposable {
953958 return models . filter ( value => value !== undefined ) as PullRequestModel [ ] ;
954959 }
955960
961+ /**
962+ * On first activation, iterate through local branches and associate them with PRs if they match.
963+ * This helps discover PRs that were created before the extension was installed or in other ways.
964+ */
965+ private async associateLocalBranchesWithPRsOnFirstActivation ( ) : Promise < void > {
966+ const stateKey = `${ BRANCHES_ASSOCIATED_WITH_PRS } .${ this . repository . rootUri . fsPath } ` ;
967+ const hasRun = this . context . globalState . get < boolean > ( stateKey , false ) ;
968+
969+ if ( hasRun ) {
970+ Logger . debug ( 'Branch association has already run for this workspace folder' , this . id ) ;
971+ return ;
972+ }
973+
974+ Logger . appendLine ( 'First activation: associating local branches with PRs' , this . id ) ;
975+
976+ const githubRepositories = this . _githubRepositories ;
977+ if ( ! githubRepositories || ! githubRepositories . length || ! this . repository . getRefs ) {
978+ Logger . debug ( 'No GitHub repositories or getRefs not available, skipping branch association' , this . id ) ;
979+ await this . context . globalState . update ( stateKey , true ) ;
980+ return ;
981+ }
982+
983+ try {
984+ const localBranches = ( await this . repository . getRefs ( { pattern : 'refs/heads/' } ) )
985+ . filter ( r => r . name !== undefined )
986+ . map ( r => r . name ! ) ;
987+
988+ Logger . debug ( `Found ${ localBranches . length } local branches to check` , this . id ) ;
989+
990+ // Process branches in chunks to avoid overwhelming the system
991+ const chunkSize = 10 ;
992+ const associationResults : boolean [ ] = [ ] ;
993+
994+ for ( let i = 0 ; i < localBranches . length ; i += chunkSize ) {
995+ const chunk = localBranches . slice ( i , i + chunkSize ) ;
996+ const chunkResults = await Promise . all ( chunk . map ( async branchName => {
997+ try {
998+ // Check if this branch already has PR metadata
999+ const existingMetadata = await PullRequestGitHelper . getMatchingPullRequestMetadataForBranch (
1000+ this . repository ,
1001+ branchName ,
1002+ ) ;
1003+
1004+ if ( existingMetadata ) {
1005+ // Branch already has PR metadata, skip
1006+ return false ;
1007+ }
1008+
1009+ // Get the branch to check its upstream
1010+ const branch = await this . repository . getBranch ( branchName ) ;
1011+ if ( ! branch . upstream ) {
1012+ // No upstream, can't match to a PR
1013+ return false ;
1014+ }
1015+
1016+ // Try to find a matching PR on GitHub
1017+ const remoteName = branch . upstream . remote ;
1018+ const upstreamBranchName = branch . upstream . name ;
1019+
1020+ const githubRepo = githubRepositories . find (
1021+ repo => repo . remote . remoteName === remoteName ,
1022+ ) ;
1023+
1024+ if ( ! githubRepo ) {
1025+ return false ;
1026+ }
1027+
1028+ // Get the metadata of the GitHub repository to find owner
1029+ const metadata = await githubRepo . getMetadata ( ) ;
1030+ if ( ! metadata ?. owner ) {
1031+ return false ;
1032+ }
1033+
1034+ // Search for a PR with this head branch
1035+ const matchingPR = await githubRepo . getPullRequestForBranch ( upstreamBranchName , metadata . owner . login ) ;
1036+
1037+ if ( matchingPR ) {
1038+ Logger . appendLine ( `Found PR #${ matchingPR . number } for branch ${ branchName } , associating...` , this . id ) ;
1039+ await PullRequestGitHelper . associateBranchWithPullRequest (
1040+ this . repository ,
1041+ matchingPR ,
1042+ branchName ,
1043+ ) ;
1044+ return true ;
1045+ }
1046+ return false ;
1047+ } catch ( e ) {
1048+ Logger . debug ( `Error checking branch ${ branchName } : ${ e } ` , this . id ) ;
1049+ // Continue with other branches even if one fails
1050+ return false ;
1051+ }
1052+ } ) ) ;
1053+ associationResults . push ( ...chunkResults ) ;
1054+ }
1055+
1056+ const associatedCount = associationResults . filter ( r => r ) . length ;
1057+ Logger . appendLine ( `Branch association complete: ${ associatedCount } branches associated with PRs` , this . id ) ;
1058+ } catch ( e ) {
1059+ Logger . error ( `Error during branch association: ${ e } ` , this . id ) ;
1060+ } finally {
1061+ // Mark as complete even if there were errors
1062+ await this . context . globalState . update ( stateKey , true ) ;
1063+ }
1064+ }
1065+
9561066 async getLabels ( issue ?: IssueModel , repoInfo ?: { owner : string ; repo : string } ) : Promise < ILabel [ ] > {
9571067 const repo = issue
9581068 ? issue . githubRepository
0 commit comments