Show latest commit message for branch in terminal suggest widget details view (#272296)

This commit is contained in:
Copilot
2025-10-27 14:53:44 +00:00
committed by GitHub
parent 0c5276f0a6
commit 6cf95b098d
2 changed files with 163 additions and 68 deletions

View File

@@ -74,46 +74,67 @@ const postProcessBranches =
const seen = new Set<string>();
return output
.split("\n")
.filter((line) => !line.trim().startsWith("HEAD"))
.filter((line) => line.trim() && !line.trim().startsWith("HEAD"))
.map((branch) => {
let name = branch.trim();
const parts = branch.match(/\S+/g);
if (parts && parts.length > 1) {
if (parts[0] === "*") {
// We are in a detached HEAD state
if (branch.includes("HEAD detached")) {
return null;
// Parse the format: branchName|author|hash|subject|timeAgo
const parts = branch.split("|");
if (parts.length < 5) {
// Fallback to old parsing if format doesn't match
let name = branch.trim();
const oldParts = branch.match(/\S+/g);
if (oldParts && oldParts.length > 1) {
if (oldParts[0] === "*") {
if (branch.includes("HEAD detached")) {
return null;
}
return {
name: branch.replaceAll("*", "").trim(),
description: "Current branch",
priority: 100,
icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`
};
} else if (oldParts[0] === "+") {
name = branch.replaceAll("+", "").trim();
}
// Current branch
return {
name: branch.replace("*", "").trim(),
description: "Current branch",
priority: 100,
icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`
};
} else if (parts[0] === "+") {
// Branch checked out in another worktree.
name = branch.replace("+", "").trim();
}
let description = "Branch";
if (insertWithoutRemotes && name.startsWith("remotes/")) {
name = name.slice(name.indexOf("/", 8) + 1);
description = "Remote branch";
}
const space = name.indexOf(" ");
if (space !== -1) {
name = name.slice(0, space);
}
return {
name,
description,
icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`,
priority: 75,
};
}
let description = "Branch";
let name = parts[0].trim();
const author = parts[1].trim();
const hash = parts[2].trim();
const subject = parts[3].trim();
const timeAgo = parts[4].trim();
const description = `${timeAgo}${author}${hash}${subject}`;
const priority = 75;
if (insertWithoutRemotes && name.startsWith("remotes/")) {
name = name.slice(name.indexOf("/", 8) + 1);
description = "Remote branch";
}
const space = name.indexOf(" ");
if (space !== -1) {
name = name.slice(0, space);
}
return {
name,
description,
icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`,
priority: 75,
priority,
};
})
.filter((suggestion) => {
@@ -128,6 +149,15 @@ const postProcessBranches =
});
};
// Common git for-each-ref arguments for branch queries with commit details
const gitBranchForEachRefArgs = [
"git",
"--no-optional-locks",
"for-each-ref",
"--sort=-committerdate",
"--format=%(refname:short)|%(authorname)|%(objectname:short)|%(subject)|%(committerdate:relative)",
] as const;
export const gitGenerators = {
// Commit history
commits: {
@@ -252,23 +282,17 @@ export const gitGenerators = {
// All branches
remoteLocalBranches: {
script: [
"git",
"--no-optional-locks",
"branch",
"-a",
"--no-color",
"--sort=-committerdate",
...gitBranchForEachRefArgs,
"refs/heads/",
"refs/remotes/",
],
postProcess: postProcessBranches({ insertWithoutRemotes: true }),
} satisfies Fig.Generator,
localBranches: {
script: [
"git",
"--no-optional-locks",
"branch",
"--no-color",
"--sort=-committerdate",
...gitBranchForEachRefArgs,
"refs/heads/",
],
postProcess: postProcessBranches({ insertWithoutRemotes: true }),
} satisfies Fig.Generator,
@@ -278,37 +302,19 @@ export const gitGenerators = {
localOrRemoteBranches: {
custom: async (tokens, executeShellCommand) => {
const pp = postProcessBranches({ insertWithoutRemotes: true });
if (tokens.includes("-r")) {
return pp?.(
(
await executeShellCommand({
command: "git",
args: [
"--no-optional-locks",
"-r",
"--no-color",
"--sort=-committerdate",
],
})
).stdout,
tokens
);
} else {
return pp?.(
(
await executeShellCommand({
command: "git",
args: [
"--no-optional-locks",
"branch",
"--no-color",
"--sort=-committerdate",
],
})
).stdout,
tokens
);
}
const refs = tokens.includes("-r") ? "refs/remotes/" : "refs/heads/";
return pp?.(
(
await executeShellCommand({
command: gitBranchForEachRefArgs[0],
args: [
...gitBranchForEachRefArgs.slice(1),
refs,
],
})
).stdout,
tokens
);
},
} satisfies Fig.Generator,

View File

@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import 'mocha';
import * as vscode from 'vscode';
import { gitGenerators } from '../../completions/git';
suite('Git Branch Completions', () => {
test('postProcessBranches should parse git for-each-ref output with commit details', () => {
const input = `main|John Doe|abc1234|Fix response codeblock in debug view|2 days ago
feature/test|Jane Smith|def5678|Add new feature|1 week ago`;
const result = gitGenerators.localBranches.postProcess!(input, []);
assert.ok(result);
assert.strictEqual(result.length, 2);
assert.ok(result[0]);
assert.strictEqual(result[0].name, 'main');
assert.strictEqual(result[0].description, '2 days ago • John Doe • abc1234 • Fix response codeblock in debug view');
assert.strictEqual(result[0].icon, `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`);
assert.ok(result[1]);
assert.strictEqual(result[1].name, 'feature/test');
assert.strictEqual(result[1].description, '1 week ago • Jane Smith • def5678 • Add new feature');
assert.strictEqual(result[1].icon, `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`);
});
test('postProcessBranches should handle remote branches', () => {
const input = `remotes/origin/main|John Doe|abc1234|Fix bug|2 days ago
remotes/origin/feature|Jane Smith|def5678|Add feature|1 week ago`;
const result = gitGenerators.remoteLocalBranches.postProcess!(input, []);
assert.ok(result);
assert.strictEqual(result.length, 2);
assert.ok(result[0]);
assert.strictEqual(result[0].name, 'main');
assert.strictEqual(result[0].description, '2 days ago • John Doe • abc1234 • Fix bug');
assert.ok(result[1]);
assert.strictEqual(result[1].name, 'feature');
assert.strictEqual(result[1].description, '1 week ago • Jane Smith • def5678 • Add feature');
});
test('postProcessBranches should filter out HEAD branches', () => {
const input = `main|John Doe|abc1234|Fix bug|2 days ago
HEAD -> main|John Doe|abc1234|Fix bug|2 days ago`;
const result = gitGenerators.localBranches.postProcess!(input, []);
assert.ok(result);
assert.strictEqual(result.length, 1);
assert.ok(result[0]);
assert.strictEqual(result[0].name, 'main');
});
test('postProcessBranches should handle empty input', () => {
const input = '';
const result = gitGenerators.localBranches.postProcess!(input, []);
assert.ok(result);
assert.strictEqual(result.length, 0);
});
test('postProcessBranches should handle git error output', () => {
const input = 'fatal: not a git repository';
const result = gitGenerators.localBranches.postProcess!(input, []);
assert.ok(result);
assert.strictEqual(result.length, 0);
});
test('postProcessBranches should deduplicate branches', () => {
const input = `main|John Doe|abc1234|Fix bug|2 days ago
main|John Doe|abc1234|Fix bug|2 days ago`;
const result = gitGenerators.localBranches.postProcess!(input, []);
assert.ok(result);
assert.strictEqual(result.length, 1);
assert.ok(result[0]);
assert.strictEqual(result[0].name, 'main');
});
});