Skip to content

Commit 0a4aae5

Browse files
Copilotalexr00
andauthored
Add recently used branches tracking for PR creation (#8300)
* Initial plan * Initial plan for supporting more branches in PR creation Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Add recently used branches feature for PR creation - Added RECENTLY_USED_BRANCHES state key and RecentlyUsedBranchesState interface - Added RECENTLY_USED_BRANCHES_COUNT setting key (default: 5, range: 0-20) - Added setting in package.json with localization - Implemented getRecentlyUsedBranches() and saveRecentlyUsedBranch() helper methods - Modified branchPicks() to show recently used branches at the top with separators - Added calls to save base branch when selected and when PR is created Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Optimize configuration reads by extracting helper method - Extract getRecentlyUsedBranchesMaxCount() to avoid redundant config reads - Both getRecentlyUsedBranches() and saveRecentlyUsedBranch() now use the helper Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> * Hard-code recently used branches limit to 10 and allow branches not in list - Remove recentlyUsedBranchesCount setting as requested - Hard-code limit to 10 branches instead of using configurable setting - Allow recently used branches even if they're not in the fetched branch list This ensures branches like 'develop' are shown even when timeout cuts off the list Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent 8fa502f commit 0a4aae5

2 files changed

Lines changed: 94 additions & 19 deletions

File tree

src/extensionState.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ 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';
1616
export const BRANCHES_ASSOCIATED_WITH_PRS = 'github.pullRequest.branchesAssociatedWithPRs';
17+
export const RECENTLY_USED_BRANCHES = 'github.pullRequest.recentlyUsedBranches';
1718

1819
export interface RepoState {
1920
mentionableUsers?: IAccount[];
@@ -24,6 +25,10 @@ export interface ReposState {
2425
repos: { [ownerAndRepo: string]: RepoState };
2526
}
2627

28+
export interface RecentlyUsedBranchesState {
29+
branches: { [ownerAndRepo: string]: string[] };
30+
}
31+
2732
export function setSyncedKeys(context: vscode.ExtensionContext) {
2833
context.globalState.setKeysForSync([NEVER_SHOW_PULL_NOTIFICATION]);
2934
}

src/github/createPRViewProvider.ts

Lines changed: 89 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import { ITelemetry } from '../common/telemetry';
4040
import { asPromise, compareIgnoreCase, formatError, promiseWithTimeout } from '../common/utils';
4141
import { generateUuid } from '../common/uuid';
4242
import { IRequestMessage, WebviewViewBase } from '../common/webview';
43-
import { PREVIOUS_CREATE_METHOD } from '../extensionState';
43+
import { PREVIOUS_CREATE_METHOD, RECENTLY_USED_BRANCHES, RecentlyUsedBranchesState } from '../extensionState';
4444
import { CreatePullRequestDataModel } from '../view/createPullRequestDataModel';
4545

4646
const ISSUE_CLOSING_KEYWORDS = new RegExp('closes|closed|close|fixes|fixed|fix|resolves|resolved|resolve\s$', 'i'); // https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
@@ -133,6 +133,33 @@ export abstract class BaseCreatePullRequestViewProvider<T extends BasePullReques
133133
return repo.getRepoAccessAndMergeMethods(refetch);
134134
}
135135

136+
protected getRecentlyUsedBranches(owner: string, repositoryName: string): string[] {
137+
const repoKey = `${owner}/${repositoryName}`;
138+
const state = this._folderRepositoryManager.context.workspaceState.get<RecentlyUsedBranchesState>(RECENTLY_USED_BRANCHES, { branches: {} });
139+
return state.branches[repoKey] || [];
140+
}
141+
142+
protected saveRecentlyUsedBranch(owner: string, repositoryName: string, branchName: string): void {
143+
const repoKey = `${owner}/${repositoryName}`;
144+
const state = this._folderRepositoryManager.context.workspaceState.get<RecentlyUsedBranchesState>(RECENTLY_USED_BRANCHES, { branches: {} });
145+
146+
// Get the current list for this repo
147+
let recentBranches = state.branches[repoKey] || [];
148+
149+
// Remove the branch if it's already in the list
150+
recentBranches = recentBranches.filter(b => b !== branchName);
151+
152+
// Add it to the front
153+
recentBranches.unshift(branchName);
154+
155+
// Limit to 10 branches
156+
recentBranches = recentBranches.slice(0, 10);
157+
158+
// Save back to state
159+
state.branches[repoKey] = recentBranches;
160+
this._folderRepositoryManager.context.workspaceState.update(RECENTLY_USED_BRANCHES, state);
161+
}
162+
136163
private initializeWhenVisibleDisposable: vscode.Disposable | undefined;
137164
public async initializeParams(reset: boolean = false): Promise<void> {
138165
if (this._view?.visible === false && this.initializeWhenVisibleDisposable === undefined) {
@@ -821,24 +848,61 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv
821848
// For the compare, we only want to show local branches.
822849
branches = (await this._folderRepositoryManager.repository.getBranches({ remote: false })).filter(branch => branch.name);
823850
}
824-
// TODO: @alexr00 - Add sorting so that the most likely to be used branch (ex main or release if base) is at the top of the list.
825-
const branchPicks: (vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[] = branches.map(branch => {
826-
const branchName = typeof branch === 'string' ? branch : branch.name!;
827-
const pick: (vscode.QuickPickItem & { remote: RemoteInfo, branch: string }) = {
828-
iconPath: new vscode.ThemeIcon('git-branch'),
829-
label: branchName,
830-
remote: {
831-
owner: githubRepository.remote.owner,
832-
repositoryName: githubRepository.remote.repositoryName
833-
},
834-
branch: branchName
835-
};
836-
return pick;
837-
});
838-
branchPicks.unshift({
839-
kind: vscode.QuickPickItemKind.Separator,
840-
label: `${githubRepository.remote.owner}/${githubRepository.remote.repositoryName}`
841-
});
851+
852+
const branchNames = branches.map(branch => typeof branch === 'string' ? branch : branch.name!);
853+
854+
// Get recently used branches for base branches only
855+
let recentBranches: string[] = [];
856+
let otherBranches: string[] = branchNames;
857+
if (isBase) {
858+
const recentlyUsed = this.getRecentlyUsedBranches(githubRepository.remote.owner, githubRepository.remote.repositoryName);
859+
// Include all recently used branches, even if they're not in the current branch list
860+
// This allows showing branches that weren't fetched due to timeout
861+
recentBranches = recentlyUsed;
862+
// Remove recently used branches from the main list (if they exist there)
863+
otherBranches = branchNames.filter(name => !recentBranches.includes(name));
864+
}
865+
866+
const branchPicks: (vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[] = [];
867+
868+
// Add recently used branches section
869+
if (recentBranches.length > 0) {
870+
branchPicks.push({
871+
kind: vscode.QuickPickItemKind.Separator,
872+
label: vscode.l10n.t('Recently Used')
873+
});
874+
recentBranches.forEach(branchName => {
875+
branchPicks.push({
876+
iconPath: new vscode.ThemeIcon('git-branch'),
877+
label: branchName,
878+
remote: {
879+
owner: githubRepository.remote.owner,
880+
repositoryName: githubRepository.remote.repositoryName
881+
},
882+
branch: branchName
883+
});
884+
});
885+
}
886+
887+
// Add all other branches section
888+
if (otherBranches.length > 0) {
889+
branchPicks.push({
890+
kind: vscode.QuickPickItemKind.Separator,
891+
label: recentBranches.length > 0 ? vscode.l10n.t('All Branches') : `${githubRepository.remote.owner}/${githubRepository.remote.repositoryName}`
892+
});
893+
otherBranches.forEach(branchName => {
894+
branchPicks.push({
895+
iconPath: new vscode.ThemeIcon('git-branch'),
896+
label: branchName,
897+
remote: {
898+
owner: githubRepository.remote.owner,
899+
repositoryName: githubRepository.remote.repositoryName
900+
},
901+
branch: branchName
902+
});
903+
});
904+
}
905+
842906
branchPicks.unshift({
843907
iconPath: new vscode.ThemeIcon('repo'),
844908
label: changeRepoMessage
@@ -856,6 +920,10 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv
856920
const baseBranchChanged = baseRemoteChanged || this.model.baseBranch !== result.branch;
857921
this.model.baseOwner = result.remote.owner;
858922
this.model.baseBranch = result.branch;
923+
924+
// Save the selected base branch to recently used branches
925+
this.saveRecentlyUsedBranch(result.remote.owner, result.remote.repositoryName, result.branch);
926+
859927
const compareBranch = await this._folderRepositoryManager.repository.getBranch(this.model.compareBranch);
860928
const [mergeConfiguration, titleAndDescription, mergeQueueMethodForBranch] = await Promise.all([
861929
this.getMergeConfiguration(result.remote.owner, result.remote.repositoryName),
@@ -1259,6 +1327,8 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv
12591327
if (!createdPR) {
12601328
this._throwError(message, vscode.l10n.t('There must be a difference in commits to create a pull request.'));
12611329
} else {
1330+
// Save the base branch to recently used branches after successful PR creation
1331+
this.saveRecentlyUsedBranch(message.args.owner, message.args.repo, message.args.base);
12621332
await this.postCreate(message, createdPR);
12631333
}
12641334
} catch (e) {

0 commit comments

Comments
 (0)