Skip to content

Commit 3bca0d2

Browse files
authored
Merge branch 'main' into copilot/confirmation-popup-for-cancel-button
2 parents 6ee5507 + b0da990 commit 3bca0d2

19 files changed

Lines changed: 381 additions & 57 deletions

.github/copilot-instructions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
- **Commands**: When adding a new command, consider whether it should be available in the command palette, context menus, or both. Add the appropriate menu entries in `package.json` to ensure the command is properly included, or excluded (command palette), from menus.
2121

2222
## Pull Request Guidelines
23-
- Never touch the yarn.lock file.
24-
- Run `yarn run lint` and also `npm run hygiene` and fix any errors or warnings before committing.
23+
- Never touch the package-lock.json file.
24+
- Run `npm run lint` and also `npm run hygiene` and fix any errors or warnings before committing.
2525

2626
## Testing
2727
- Use `describe` and `it` blocks from Mocha for structuring tests.

.husky/pre-commit

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
11
#!/usr/bin/env sh
22
. "$(dirname -- "$0")/_/husky.sh"
33

4-
npm run hygiene
4+
# Hygiene reads staged contents while ESLint checks the working tree, so they can run concurrently.
5+
node ./build/hygiene.js &
6+
hygiene_pid=$!
7+
lint_pid=''
58

69
# Lint only staged TS/TSX files instead of the whole repo (much faster).
710
# Stream a NUL-delimited list directly to xargs -0 so paths with spaces/newlines
811
# are handled safely (command substitution would strip NUL bytes).
912
if git diff --cached --name-only --diff-filter=ACMR -- '*.ts' '*.tsx' | grep -q .; then
10-
git diff --cached --name-only --diff-filter=ACMR -z -- '*.ts' '*.tsx' | xargs -0 npx eslint --fix --cache
13+
git diff --cached --name-only --diff-filter=ACMR -z -- '*.ts' '*.tsx' | xargs -0 node ./node_modules/eslint/bin/eslint.js --fix --cache &
14+
lint_pid=$!
1115
fi
16+
17+
hygiene_status=0
18+
if wait "$hygiene_pid"; then
19+
:
20+
else
21+
hygiene_status=$?
22+
fi
23+
24+
lint_status=0
25+
if [ -n "$lint_pid" ]; then
26+
if wait "$lint_pid"; then
27+
:
28+
else
29+
lint_status=$?
30+
fi
31+
fi
32+
33+
if [ "$hygiene_status" -ne 0 ]; then
34+
exit "$hygiene_status"
35+
fi
36+
37+
exit "$lint_status"

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## 0.160.0
4+
5+
### Changes
6+
7+
- Added a `"githubIssues.issueCompletionFormatEditor"` setting to control the format of issue completions in the editor, mirroring the existing `"githubIssues.issueCompletionFormatScm"` setting. It defaults to `${issueNumberLabel}`, which inserts only the issue number (e.g. `#421`) so that GitHub's auto-close references keep working.
8+
9+
### Fixes
10+
11+
- "Delete local branches" is showing branches that have already been deleted. https://github.com/microsoft/vscode-pull-request-github/issues/8815
12+
313
## 0.158.0
414

515
### Changes

package-lock.json

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"treeItemMarkdownLabel",
4242
"treeViewMarkdownMessage"
4343
],
44-
"version": "0.158.0",
44+
"version": "0.160.0",
4545
"publisher": "GitHub",
4646
"engines": {
4747
"node": ">=20",
@@ -751,6 +751,11 @@
751751
"default": "${issueTitle}\nFixes ${issueNumberLabel}",
752752
"markdownDescription": "%githubIssues.issueCompletionFormatScm.markdownDescription%"
753753
},
754+
"githubIssues.issueCompletionFormatEditor": {
755+
"type": "string",
756+
"default": "${issueNumberLabel}",
757+
"markdownDescription": "%githubIssues.issueCompletionFormatEditor.markdownDescription%"
758+
},
754759
"githubIssues.workingIssueFormatScm": {
755760
"type": "string",
756761
"default": "${issueTitle} \nFixes ${issueNumberLabel}",
@@ -829,6 +834,11 @@
829834
"default": "author",
830835
"description": "%githubIssues.issueAvatarDisplay.description%"
831836
},
837+
"githubIssues.showIssueNumberInTree": {
838+
"type": "boolean",
839+
"default": false,
840+
"description": "%githubIssues.showIssueNumberInTree.description%"
841+
},
832842
"githubPullRequests.focusedMode": {
833843
"properties": {
834844
"oneOf": [
@@ -4323,7 +4333,7 @@
43234333
"debounce": "^1.2.1",
43244334
"events": "3.2.0",
43254335
"fast-deep-equal": "^3.1.3",
4326-
"js-yaml": "^4.2.0",
4336+
"js-yaml": "^4.3.0",
43274337
"jsonc-parser": "^3.3.1",
43284338
"lru-cache": "6.0.0",
43294339
"markdown-it": "^14.2.0",

package.nls.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"githubPullRequests.autoRepositoryDetection.markdownDescription": "Controls which repositories are automatically detected and opened by the extension.",
3232
"githubPullRequests.autoRepositoryDetection.workspace": "Only detect repositories within the current workspace folders.",
3333
"githubPullRequests.autoRepositoryDetection.true": "Detect all repositories found by the Git extension, including those outside workspace folders.",
34-
"githubPullRequests.queries.markdownDescription": "Specifies what queries should be used in the GitHub Pull Requests tree. All queries are made against **the currently opened repos**. Each query object has a `label` that will be shown in the tree and a search `query` using [GitHub search syntax](https://help.github.com/en/articles/understanding-the-search-syntax). By default these queries define the categories \"Copilot on My Behalf\", \"Local Pull Request Branches\", \"Waiting For My Review\", \"Assigned To Me\" and \"Created By Me\". If you want to preserve these, make sure they are still in the array when you modify the setting. \n\n**Variables available:**\n - `${user}` - currently logged in user \n - `${owner}` - repository owner, ex. `microsoft` in `microsoft/vscode` \n - `${repository}` - repository name, ex. `vscode` in `microsoft/vscode` \n - `${today-Nd}` - date N days ago, ex. `${today-7d}` becomes `2025-01-04`\n\n**Example custom queries:**\n```json\n\"githubPullRequests.queries\": [\n {\n \"label\": \"Waiting For My Review\",\n \"query\": \"is:open review-requested:${user}\"\n },\n {\n \"label\": \"Mentioned Me\",\n \"query\": \"is:open mentions:${user}\"\n },\n {\n \"label\": \"Recent Activity\",\n \"query\": \"is:open updated:>${today-7d}\"\n }\n]\n```",
34+
"githubPullRequests.queries.markdownDescription": "Specifies what queries should be used in the GitHub Pull Requests tree. All queries are made against **the currently opened repos**. Each query object has a `label` that will be shown in the tree and a search `query` using [GitHub search syntax](https://help.github.com/en/articles/understanding-the-search-syntax). By default these queries define the categories \"Copilot on My Behalf\", \"Local Pull Request Branches\", \"Waiting For My Review\", \"Assigned To Me\" and \"Created By Me\". If you want to preserve these, make sure they are still in the array when you modify the setting. \n\n**Variables available:**\n - `${user}` - currently logged in user \n - `${owner}` - repository owner, ex. `microsoft` in `microsoft/vscode` \n - `${repository}` - repository name, ex. `vscode` in `microsoft/vscode` \n - `${today-Nd}` - date N days ago, ex. `${today-7d}` becomes `2025-01-04`\n\n**Matching multiple values:** Queries use GitHub's advanced search, where a space between qualifiers means `AND`. To match any one of several values (for example several authors), combine them with `OR`, ex. `author:name1 OR author:name2 OR author:name3`.\n\n**Example custom queries:**\n```json\n\"githubPullRequests.queries\": [\n {\n \"label\": \"Waiting For My Review\",\n \"query\": \"is:open review-requested:${user}\"\n },\n {\n \"label\": \"Mentioned Me\",\n \"query\": \"is:open mentions:${user}\"\n },\n {\n \"label\": \"Created By My Team\",\n \"query\": \"is:open author:name1 OR author:name2 OR author:name3\"\n },\n {\n \"label\": \"Recent Activity\",\n \"query\": \"is:open updated:>${today-7d}\"\n }\n]\n```",
3535
"githubPullRequests.queries.label.description": "The label to display for the query in the Pull Requests tree.",
3636
"githubPullRequests.queries.query.description": "The GitHub search query for finding pull requests. Use GitHub search syntax with variables like ${user}, ${owner}, ${repository}. Example: 'is:open author:${user}' finds your open pull requests.",
3737
"githubPullRequests.queries.copilotOnMyBehalf": "Copilot on My Behalf",
@@ -158,6 +158,12 @@
158158
"Do not translate what's inside of the ${...}. It is an internal syntax for the extension."
159159
]
160160
},
161+
"githubIssues.issueCompletionFormatEditor.markdownDescription": {
162+
"message": "Sets the format of issue completions in the editor. \n- `${user}` will be replaced with the currently logged in username \n- `${issueNumber}` will be replaced with the current issue number \n- `${issueNumberLabel}` will be replaced with a label formatted as #number or owner/repository#number, depending on whether the issue is in the current repository \n- `${issueTitle}` will be replaced with the issue title",
163+
"comment": [
164+
"Do not translate what's inside of the ${...}. It is an internal syntax for the extension."
165+
]
166+
},
161167
"githubIssues.workingIssueFormatScm.markdownDescription": {
162168
"message": "Sets the format of the commit message that is set in the SCM inputbox when you **Start Working on an Issue**. Defaults to `${issueTitle} \nFixes ${issueNumberLabel}`",
163169
"comment": [
@@ -177,6 +183,7 @@
177183
"githubIssues.issueAvatarDisplay.description": "Controls which avatar to display in the issue list.",
178184
"githubIssues.issueAvatarDisplay.author": "Show the avatar of the issue creator.",
179185
"githubIssues.issueAvatarDisplay.assignee": "Show the avatar of the first assignee (show GitHub icon if no assignees).",
186+
"githubIssues.showIssueNumberInTree.description": "Shows the issue number in the tree view.",
180187
"githubPullRequests.focusedMode.description": "The layout to use when a pull request is checked out. Set to false to prevent layout changes.",
181188
"githubPullRequests.focusedMode.firstDiff": "Show the first diff in the pull request. If there are no changes, show the overview.",
182189
"githubPullRequests.focusedMode.overview": "Show the overview of the pull request.",

src/@types/vscode.proposed.chatParticipantAdditions.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,11 @@ declare module 'vscode' {
331331
*/
332332
result?: string;
333333

334+
/**
335+
* The display name of the model used by the subagent.
336+
*/
337+
modelName?: string;
338+
334339
constructor(description?: string, agentName?: string, prompt?: string, result?: string);
335340
}
336341

src/common/settingKeys.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,14 @@ export const WORKING_BASE_BRANCH = 'workingBaseBranch';
6262
export const WORKING_ISSUE_FORMAT_SCM = 'workingIssueFormatScm';
6363
export const IGNORE_COMPLETION_TRIGGER = 'ignoreCompletionTrigger';
6464
export const ISSUE_COMPLETION_FORMAT_SCM = 'issueCompletionFormatScm';
65+
export const ISSUE_COMPLETION_FORMAT_EDITOR = 'issueCompletionFormatEditor';
6566
export const CREATE_ISSUE_TRIGGERS = 'createIssueTriggers';
6667
export const DEFAULT = 'default';
6768
export const IGNORE_MILESTONES = 'ignoreMilestones';
6869
export const ALLOW_FETCH = 'allowFetch';
6970
export const ALWAYS_PROMPT_FOR_NEW_ISSUE_REPO = 'alwaysPromptForNewIssueRepo';
7071
export const ISSUE_AVATAR_DISPLAY = 'issueAvatarDisplay';
72+
export const SHOW_ISSUE_NUMBER_IN_TREE = 'showIssueNumberInTree';
7173
export const EXPERIMENTAL_CHAT = 'experimental.chat';
7274
export const EXPERIMENTAL_USE_QUICK_CHAT = 'experimental.useQuickChat';
7375
export const EXPERIMENTAL_NOTIFICATIONS_PAGE_SIZE = 'experimental.notificationsViewPageSize';

src/github/folderRepositoryManager.ts

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2099,13 +2099,19 @@ export class FolderRepositoryManager extends Disposable {
20992099
const deleteConfig = async (branch: string) => {
21002100
await PullRequestGitHelper.associateBaseBranchWithBranch(this.repository, branch, undefined);
21012101
await PullRequestGitHelper.associateBranchWithPullRequest(this.repository, undefined, branch);
2102+
if (this.repository.unsetConfig) {
2103+
const prefix = `branch.${branch}.`;
2104+
const remaining = (await this.repository.getConfigs()).filter(config => config.key.startsWith(prefix));
2105+
for (const config of remaining) {
2106+
try {
2107+
await this.repository.unsetConfig(config.key);
2108+
} catch (e) {
2109+
Logger.error(`Failed to remove leftover git config ${config.key}: ${e}`, this.id);
2110+
}
2111+
}
2112+
}
21022113
};
21032114

2104-
// delete configs first since that can't be parallelized
2105-
for (const pick of picks) {
2106-
await deleteConfig(pick.label);
2107-
}
2108-
21092115
// batch deleting the branches to avoid consuming all available resources
21102116
await batchPromiseAll(picks, 5, async (pick) => {
21112117
try {
@@ -2126,11 +2132,50 @@ export class FolderRepositoryManager extends Disposable {
21262132
}
21272133
}
21282134
});
2135+
2136+
// Git removes the whole config section when deleting a branch, including keys with multiple values.
2137+
// Recreate deleted branches when a provider leaves config behind so Git can perform that cleanup again.
2138+
const configsAfterDeletion = await this.repository.getConfigs();
2139+
const branchesNeedingConfigCleanup = picks.filter(pick => {
2140+
const prefix = `branch.${pick.label}.`;
2141+
return configsAfterDeletion.some(config => config.key.startsWith(prefix));
2142+
});
2143+
const branchesNeedingRetry = new Set(needsRetry?.map(pick => pick.label));
2144+
for (const pick of branchesNeedingConfigCleanup) {
2145+
if (!branchesNeedingRetry.has(pick.label)) {
2146+
try {
2147+
await this.repository.createBranch(pick.label, false, 'HEAD');
2148+
await this.repository.deleteBranch(pick.label, true);
2149+
} catch (e) {
2150+
Logger.error(`Failed to remove leftover git config section for ${pick.label}: ${e}`, this.id);
2151+
}
2152+
}
2153+
}
2154+
2155+
// Config operations can't be parallelized. Clean up any keys left by providers that don't remove sections.
2156+
for (const pick of picks) {
2157+
if (!branchesNeedingRetry.has(pick.label)) {
2158+
await deleteConfig(pick.label);
2159+
}
2160+
}
21292161
if (needsRetry && needsRetry.length) {
21302162
await this.deleteBranches(needsRetry, nonExistantBranches, progress, totalBranches, deletedBranches);
21312163
}
21322164
}
21332165

2166+
private async deleteRemotes(picks: readonly vscode.QuickPickItem[]): Promise<{ label: string; error: unknown }[]> {
2167+
const failures: { label: string; error: unknown }[] = [];
2168+
for (const pick of picks) {
2169+
try {
2170+
await this.repository.removeRemote(pick.label);
2171+
} catch (error) {
2172+
Logger.error(`Failed to delete remote ${pick.label}: ${formatError(error)}`, this.id);
2173+
failures.push({ label: pick.label, error });
2174+
}
2175+
}
2176+
return failures;
2177+
}
2178+
21342179
async deleteLocalBranchesNRemotes() {
21352180
return new Promise<void>(async resolve => {
21362181
const quickPick = vscode.window.createQuickPick();
@@ -2238,13 +2283,14 @@ export class FolderRepositoryManager extends Disposable {
22382283
}
22392284
await deleteBranchesAndShowRemoteStep();
22402285
} else {
2241-
// batch deleting the remotes to avoid consuming all available resources
22422286
const picks = quickPick.selectedItems;
22432287
if (picks.length) {
22442288
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Deleting {0} remotes...', picks.length) }, async () => {
2245-
await batchPromiseAll(picks, 5, async pick => {
2246-
await this.repository.removeRemote(pick.label);
2247-
});
2289+
const failures = await this.deleteRemotes(picks);
2290+
if (failures.length) {
2291+
const message = failures.map(({ label, error }) => `${label}: ${formatError(error)}`).join('\n');
2292+
await vscode.window.showErrorMessage(vscode.l10n.t('Deleting remotes failed:\n{0}', message));
2293+
}
22482294
});
22492295
}
22502296
quickPick.hide();

src/issues/issueCompletionProvider.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import * as vscode from 'vscode';
77
import {
88
IGNORE_COMPLETION_TRIGGER,
9+
ISSUE_COMPLETION_FORMAT_EDITOR,
910
ISSUE_COMPLETION_FORMAT_SCM,
1011
ISSUES_SETTINGS_NAMESPACE,
1112
} from '../common/settingKeys';
@@ -208,11 +209,15 @@ export class IssueCompletionProvider implements vscode.CompletionItemProvider {
208209
if (document.languageId === 'markdown') {
209210
item.insertText = `[${getIssueNumberLabel(issue, repo)}](${issue.html_url})`;
210211
} else {
211-
const configuration = vscode.workspace
212+
let completionFormatSetting = ISSUE_COMPLETION_FORMAT_EDITOR;
213+
if (document.uri.path.match(/git\/scm\d\/input/)) {
214+
completionFormatSetting = ISSUE_COMPLETION_FORMAT_SCM;
215+
}
216+
const completionFormat = vscode.workspace
212217
.getConfiguration(ISSUES_SETTINGS_NAMESPACE)
213-
.get(ISSUE_COMPLETION_FORMAT_SCM);
214-
if (document.uri.path.match(/git\/scm\d\/input/) && typeof configuration === 'string') {
215-
item.insertText = variableSubstitution(configuration, issue, repo);
218+
.get(completionFormatSetting);
219+
if (typeof completionFormat === 'string') {
220+
item.insertText = variableSubstitution(completionFormat, issue, repo);
216221
} else {
217222
item.insertText = `${getIssueNumberLabel(issue, repo)}`;
218223
}

0 commit comments

Comments
 (0)