Skip to content

Commit 226428b

Browse files
Copilotalexr00
andcommitted
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>
1 parent 87cdab6 commit 226428b

File tree

5 files changed

+114
-20
lines changed

5 files changed

+114
-20
lines changed

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@
198198
"minimum": 1000,
199199
"markdownDescription": "%githubPullRequests.branchListTimeout.description%"
200200
},
201+
"githubPullRequests.recentlyUsedBranchesCount": {
202+
"type": "number",
203+
"default": 5,
204+
"minimum": 0,
205+
"maximum": 20,
206+
"markdownDescription": "%githubPullRequests.recentlyUsedBranchesCount.description%"
207+
},
201208
"githubPullRequests.remotes": {
202209
"type": "array",
203210
"default": [

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
]
2222
},
2323
"githubPullRequests.branchListTimeout.description": "Maximum time in milliseconds to wait when fetching the list of branches for pull request creation. Repositories with thousands of branches may need a higher value to ensure all branches (including the default branch) are retrieved. Minimum value is 1000 (1 second).",
24+
"githubPullRequests.recentlyUsedBranchesCount.description": "The maximum number of recently used base branches to track and show at the top of the branch picker when creating a pull request. Set to 0 to disable this feature.",
2425
"githubPullRequests.codingAgent.description": "Enables integration with the asynchronous Copilot coding agent. The '#copilotCodingAgent' tool will be available in agent mode when this setting is enabled.",
2526
"githubPullRequests.codingAgent.uiIntegration.description": "Enables UI integration within VS Code to create new coding agent sessions.",
2627
"githubPullRequests.codingAgent.autoCommitAndPush.description": "Allow automatic git operations (commit, push) to be performed when starting a coding agent session",

src/common/settingKeys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const PR_SETTINGS_NAMESPACE = 'githubPullRequests';
77
export const TERMINAL_LINK_HANDLER = 'terminalLinksHandler';
88
export const BRANCH_PUBLISH = 'createOnPublishBranch';
99
export const BRANCH_LIST_TIMEOUT = 'branchListTimeout';
10+
export const RECENTLY_USED_BRANCHES_COUNT = 'recentlyUsedBranchesCount';
1011
export const USE_REVIEW_MODE = 'useReviewMode';
1112
export const FILE_LIST_LAYOUT = 'fileListLayout';
1213
export const HIDE_VIEWED_FILES = 'hideViewedFiles';

src/extensionState.ts

Lines changed: 5 additions & 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 RECENTLY_USED_BRANCHES = 'github.pullRequest.recentlyUsedBranches';
1617

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

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

src/github/createPRViewProvider.ts

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ import {
3434
PR_SETTINGS_NAMESPACE,
3535
PULL_REQUEST_DESCRIPTION,
3636
PULL_REQUEST_LABELS,
37-
PUSH_BRANCH
37+
PUSH_BRANCH,
38+
RECENTLY_USED_BRANCHES_COUNT
3839
} from '../common/settingKeys';
3940
import { ITelemetry } from '../common/telemetry';
4041
import { asPromise, compareIgnoreCase, formatError, promiseWithTimeout } from '../common/utils';
4142
import { generateUuid } from '../common/uuid';
4243
import { IRequestMessage, WebviewViewBase } from '../common/webview';
43-
import { PREVIOUS_CREATE_METHOD } from '../extensionState';
44+
import { PREVIOUS_CREATE_METHOD, RECENTLY_USED_BRANCHES, RecentlyUsedBranchesState } from '../extensionState';
4445
import { CreatePullRequestDataModel } from '../view/createPullRequestDataModel';
4546

4647
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 +134,43 @@ export abstract class BaseCreatePullRequestViewProvider<T extends BasePullReques
133134
return repo.getRepoAccessAndMergeMethods(refetch);
134135
}
135136

137+
protected getRecentlyUsedBranches(owner: string, repositoryName: string): string[] {
138+
const maxCount = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<number>(RECENTLY_USED_BRANCHES_COUNT, 5);
139+
if (maxCount === 0) {
140+
return [];
141+
}
142+
143+
const repoKey = `${owner}/${repositoryName}`;
144+
const state = this._folderRepositoryManager.context.workspaceState.get<RecentlyUsedBranchesState>(RECENTLY_USED_BRANCHES, { branches: {} });
145+
return state.branches[repoKey] || [];
146+
}
147+
148+
protected saveRecentlyUsedBranch(owner: string, repositoryName: string, branchName: string): void {
149+
const maxCount = vscode.workspace.getConfiguration(PR_SETTINGS_NAMESPACE).get<number>(RECENTLY_USED_BRANCHES_COUNT, 5);
150+
if (maxCount === 0) {
151+
return;
152+
}
153+
154+
const repoKey = `${owner}/${repositoryName}`;
155+
const state = this._folderRepositoryManager.context.workspaceState.get<RecentlyUsedBranchesState>(RECENTLY_USED_BRANCHES, { branches: {} });
156+
157+
// Get the current list for this repo
158+
let recentBranches = state.branches[repoKey] || [];
159+
160+
// Remove the branch if it's already in the list
161+
recentBranches = recentBranches.filter(b => b !== branchName);
162+
163+
// Add it to the front
164+
recentBranches.unshift(branchName);
165+
166+
// Limit to maxCount
167+
recentBranches = recentBranches.slice(0, maxCount);
168+
169+
// Save back to state
170+
state.branches[repoKey] = recentBranches;
171+
this._folderRepositoryManager.context.workspaceState.update(RECENTLY_USED_BRANCHES, state);
172+
}
173+
136174
private initializeWhenVisibleDisposable: vscode.Disposable | undefined;
137175
public async initializeParams(reset: boolean = false): Promise<void> {
138176
if (this._view?.visible === false && this.initializeWhenVisibleDisposable === undefined) {
@@ -821,24 +859,60 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv
821859
// For the compare, we only want to show local branches.
822860
branches = (await this._folderRepositoryManager.repository.getBranches({ remote: false })).filter(branch => branch.name);
823861
}
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-
});
862+
863+
const branchNames = branches.map(branch => typeof branch === 'string' ? branch : branch.name!);
864+
865+
// Get recently used branches for base branches only
866+
let recentBranches: string[] = [];
867+
let otherBranches: string[] = branchNames;
868+
if (isBase) {
869+
const recentlyUsed = this.getRecentlyUsedBranches(githubRepository.remote.owner, githubRepository.remote.repositoryName);
870+
// Filter to only include branches that exist in the current branch list
871+
recentBranches = recentlyUsed.filter(recent => branchNames.includes(recent));
872+
// Remove recently used branches from the main list
873+
otherBranches = branchNames.filter(name => !recentBranches.includes(name));
874+
}
875+
876+
const branchPicks: (vscode.QuickPickItem & { remote?: RemoteInfo, branch?: string })[] = [];
877+
878+
// Add recently used branches section
879+
if (recentBranches.length > 0) {
880+
branchPicks.push({
881+
kind: vscode.QuickPickItemKind.Separator,
882+
label: vscode.l10n.t('Recently Used')
883+
});
884+
recentBranches.forEach(branchName => {
885+
branchPicks.push({
886+
iconPath: new vscode.ThemeIcon('git-branch'),
887+
label: branchName,
888+
remote: {
889+
owner: githubRepository.remote.owner,
890+
repositoryName: githubRepository.remote.repositoryName
891+
},
892+
branch: branchName
893+
});
894+
});
895+
}
896+
897+
// Add all other branches section
898+
if (otherBranches.length > 0) {
899+
branchPicks.push({
900+
kind: vscode.QuickPickItemKind.Separator,
901+
label: recentBranches.length > 0 ? vscode.l10n.t('All Branches') : `${githubRepository.remote.owner}/${githubRepository.remote.repositoryName}`
902+
});
903+
otherBranches.forEach(branchName => {
904+
branchPicks.push({
905+
iconPath: new vscode.ThemeIcon('git-branch'),
906+
label: branchName,
907+
remote: {
908+
owner: githubRepository.remote.owner,
909+
repositoryName: githubRepository.remote.repositoryName
910+
},
911+
branch: branchName
912+
});
913+
});
914+
}
915+
842916
branchPicks.unshift({
843917
iconPath: new vscode.ThemeIcon('repo'),
844918
label: changeRepoMessage
@@ -856,6 +930,10 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv
856930
const baseBranchChanged = baseRemoteChanged || this.model.baseBranch !== result.branch;
857931
this.model.baseOwner = result.remote.owner;
858932
this.model.baseBranch = result.branch;
933+
934+
// Save the selected base branch to recently used branches
935+
this.saveRecentlyUsedBranch(result.remote.owner, result.remote.repositoryName, result.branch);
936+
859937
const compareBranch = await this._folderRepositoryManager.repository.getBranch(this.model.compareBranch);
860938
const [mergeConfiguration, titleAndDescription, mergeQueueMethodForBranch] = await Promise.all([
861939
this.getMergeConfiguration(result.remote.owner, result.remote.repositoryName),
@@ -1259,6 +1337,8 @@ export class CreatePullRequestViewProvider extends BaseCreatePullRequestViewProv
12591337
if (!createdPR) {
12601338
this._throwError(message, vscode.l10n.t('There must be a difference in commits to create a pull request.'));
12611339
} else {
1340+
// Save the base branch to recently used branches after successful PR creation
1341+
this.saveRecentlyUsedBranch(message.args.owner, message.args.repo, message.args.base);
12621342
await this.postCreate(message, createdPR);
12631343
}
12641344
} catch (e) {

0 commit comments

Comments
 (0)