Skip to content

Commit c682ae3

Browse files
Copilotalexr00
andcommitted
Implement branch association logic for first activation
Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent 6d193a5 commit c682ae3

File tree

2 files changed

+112
-1
lines changed

2 files changed

+112
-1
lines changed

src/extensionState.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const NEVER_SHOW_PULL_NOTIFICATION = 'github.pullRequest.pullNotification
1313
export const REPO_KEYS = 'github.pullRequest.repos';
1414
export const PREVIOUS_CREATE_METHOD = 'github.pullRequest.previousCreateMethod';
1515
export const LAST_USED_EMAIL = 'github.pullRequest.lastUsedEmail';
16+
export const BRANCHES_ASSOCIATED_WITH_PRS = 'github.pullRequest.branchesAssociatedWithPRs';
1617

1718
export interface RepoState {
1819
mentionableUsers?: IAccount[];

src/github/folderRepositoryManager.ts

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import { EventType } from '../common/timelineEvent';
5454
import { Schemes } from '../common/uri';
5555
import { AsyncPredicate, batchPromiseAll, compareIgnoreCase, formatError, Predicate } from '../common/utils';
5656
import { 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';
5858
import { git } from '../gitProviders/gitCommands';
5959
import { IThemeWatcher } from '../themeWatcher';
6060
import { 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

Comments
 (0)