Skip to content

Commit 48058a2

Browse files
committed
Adds explain commit command
1 parent 18466db commit 48058a2

File tree

8 files changed

+172
-0
lines changed

8 files changed

+172
-0
lines changed

contributions.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@
2323
]
2424
}
2525
},
26+
"gitlens.ai.explainCommit": {
27+
"label": "Explain Commit",
28+
"commandPalette": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
29+
"menus": {
30+
"view/item/context": [
31+
{
32+
"when": "viewItem =~ /gitlens:commit\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
33+
"group": "3_gitlens_ai",
34+
"order": 1
35+
}
36+
]
37+
}
38+
},
2639
"gitlens.ai.generateChangelog": {
2740
"label": "Generate Changelog (Preview)...",
2841
"commandPalette": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled"

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6040,6 +6040,11 @@
60406040
"category": "GitLens",
60416041
"icon": "$(person-add)"
60426042
},
6043+
{
6044+
"command": "gitlens.ai.explainCommit",
6045+
"title": "Explain Commit",
6046+
"category": "GitLens"
6047+
},
60436048
{
60446049
"command": "gitlens.ai.generateChangelog",
60456050
"title": "Generate Changelog (Preview)...",
@@ -10343,6 +10348,10 @@
1034310348
"command": "gitlens.addAuthors",
1034410349
"when": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders"
1034510350
},
10351+
{
10352+
"command": "gitlens.ai.explainCommit",
10353+
"when": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled"
10354+
},
1034610355
{
1034710356
"command": "gitlens.ai.generateChangelog",
1034810357
"when": "gitlens:enabled && !gitlens:untrusted && gitlens:gk:organization:ai:enabled"
@@ -16555,6 +16564,11 @@
1655516564
"when": "viewItem =~ /gitlens:(compare:results(?!:)\\b(?!.*?\\b\\+filtered\\b)|commit|stash|results:files|status-branch:files|status:upstream:(ahead|behind))\\b/ && !listMultiSelection",
1655616565
"group": "2_gitlens_quickopen@1"
1655716566
},
16567+
{
16568+
"command": "gitlens.ai.explainCommit",
16569+
"when": "viewItem =~ /gitlens:commit\\b/ && !listMultiSelection && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled",
16570+
"group": "3_gitlens_ai@1"
16571+
},
1655816572
{
1655916573
"command": "gitlens.showInDetailsView",
1656016574
"when": "viewItem =~ /gitlens:(commit|stash)\\b/ && !listMultiSelection",

src/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import './commands/diffWithRevision';
2121
import './commands/diffWithRevisionFrom';
2222
import './commands/diffWithWorking';
2323
import './commands/externalDiff';
24+
import './commands/explainCommit';
2425
import './commands/generateChangelog';
2526
import './commands/generateCommitMessage';
2627
import './commands/ghpr/openOrCreateWorktree';

src/commands/explainCommit.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import type { TextEditor, Uri } from 'vscode';
2+
import { ProgressLocation } from 'vscode';
3+
import type { Container } from '../container';
4+
import { GitUri } from '../git/gitUri';
5+
import { showGenericErrorMessage } from '../messages';
6+
import type { AIExplainSource } from '../plus/ai/aiProviderService';
7+
import { ReferencesQuickPickIncludes, showReferencePicker } from '../quickpicks/referencePicker';
8+
import { getBestRepositoryOrShowPicker } from '../quickpicks/repositoryPicker';
9+
import { command } from '../system/-webview/command';
10+
import { showMarkdownPreview } from '../system/-webview/markdown';
11+
import { Logger } from '../system/logger';
12+
import { getNodeRepoPath } from '../views/nodes/abstract/viewNode';
13+
import { GlCommandBase } from './commandBase';
14+
import { getCommandUri } from './commandBase.utils';
15+
import type { CommandContext } from './commandContext';
16+
import { isCommandContextViewNodeHasCommit } from './commandContext.utils';
17+
18+
export interface ExplainCommitCommandArgs {
19+
repoPath?: string | Uri;
20+
ref?: string;
21+
source?: AIExplainSource;
22+
}
23+
24+
@command()
25+
export class ExplainCommitCommand extends GlCommandBase {
26+
constructor(private readonly container: Container) {
27+
super('gitlens.ai.explainCommit');
28+
}
29+
30+
protected override preExecute(context: CommandContext, args?: ExplainCommitCommandArgs): Promise<void> {
31+
// Check if the command is being called from a CommitNode
32+
if (isCommandContextViewNodeHasCommit(context)) {
33+
args = { ...args };
34+
args.repoPath = args.repoPath ?? getNodeRepoPath(context.node);
35+
args.ref = args.ref ?? context.node.commit.sha;
36+
args.source = args.source ?? { source: 'view', type: 'commit' };
37+
}
38+
39+
return this.execute(context.editor, context.uri, args);
40+
}
41+
42+
async execute(editor?: TextEditor, uri?: Uri, args?: ExplainCommitCommandArgs): Promise<void> {
43+
args = { ...args };
44+
45+
let repository;
46+
if (args?.repoPath != null) {
47+
repository = this.container.git.getRepository(args.repoPath);
48+
}
49+
50+
if (repository == null) {
51+
uri = getCommandUri(uri, editor);
52+
const gitUri = uri != null ? await GitUri.fromUri(uri) : undefined;
53+
repository = await getBestRepositoryOrShowPicker(
54+
gitUri,
55+
editor,
56+
'Explain Commit',
57+
'Choose which repository to explain a commit from',
58+
);
59+
}
60+
61+
if (repository == null) return;
62+
63+
try {
64+
// If no ref is provided, show a picker to select a commit
65+
if (args.ref == null) {
66+
const pick = await showReferencePicker(
67+
repository.path,
68+
'Explain Commit',
69+
'Choose a commit to explain',
70+
{
71+
allowRevisions: true,
72+
include: ReferencesQuickPickIncludes.BranchesAndTags,
73+
sort: { branches: { current: true }, tags: {} },
74+
},
75+
);
76+
if (pick?.ref == null) return;
77+
args.ref = pick.ref;
78+
}
79+
80+
// Get the commit
81+
const commit = await repository.git.commits().getCommit(args.ref);
82+
if (commit == null) {
83+
void showGenericErrorMessage('Unable to find the specified commit');
84+
return;
85+
}
86+
87+
// Call the AI service to explain the commit
88+
const result = await this.container.ai.explainCommit(
89+
commit,
90+
args.source ?? { source: 'commandPalette', type: 'commit' },
91+
{
92+
progress: { location: ProgressLocation.Notification, title: 'Explaining commit...' },
93+
},
94+
);
95+
96+
// Display the result
97+
let content = `# Commit Summary\n\n`;
98+
if (result != null) {
99+
content += `> Generated by ${result.model.name}\n\n## ${commit.summary} (${commit.shortSha})\n\n${result?.parsed.summary}\n\n${result?.parsed.body}`;
100+
} else {
101+
content += `> No changes found to explain.`;
102+
}
103+
void showMarkdownPreview(content);
104+
} catch (ex) {
105+
Logger.error(ex, 'ExplainCommitCommand', 'execute');
106+
void showGenericErrorMessage('Unable to explain commit');
107+
}
108+
}
109+
}

src/commands/quickCommand.steps.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
CommitCompareWithWorkingCommandQuickPickItem,
5353
CommitCopyIdQuickPickItem,
5454
CommitCopyMessageQuickPickItem,
55+
CommitExplainCommandQuickPickItem,
5556
CommitFileQuickPickItem,
5657
CommitFilesQuickPickItem,
5758
CommitOpenAllChangesCommandQuickPickItem,
@@ -2062,6 +2063,8 @@ async function getShowCommitOrStashStepItems<
20622063
new CommitCopyMessageQuickPickItem(state.reference),
20632064
);
20642065
} else {
2066+
items.push(createQuickPickSeparator(), new CommitExplainCommandQuickPickItem(state.reference));
2067+
20652068
const remotes = await state.repo.git.remotes().getRemotesWithProviders({ sort: true });
20662069
if (remotes?.length) {
20672070
items.push(
@@ -2400,6 +2403,8 @@ async function getShowCommitOrStashFileStepItems<
24002403
new CommitCopyMessageQuickPickItem(state.reference),
24012404
);
24022405
} else {
2406+
items.push(createQuickPickSeparator(), new CommitExplainCommandQuickPickItem(state.reference));
2407+
24032408
const remotes = await state.repo.git.remotes().getRemotesWithProviders({ sort: true });
24042409
if (remotes?.length) {
24052410
items.push(

src/constants.commands.generated.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export type ContributedCommands =
7171
| 'gitlens.graph.createWorktree'
7272
| 'gitlens.graph.deleteBranch'
7373
| 'gitlens.graph.deleteTag'
74+
| 'gitlens.graph.explainCommit'
7475
| 'gitlens.graph.fetch'
7576
| 'gitlens.graph.hideLocalBranch'
7677
| 'gitlens.graph.hideRefGroup'
@@ -611,6 +612,7 @@ export type ContributedCommands =
611612

612613
export type ContributedPaletteCommands =
613614
| 'gitlens.addAuthors'
615+
| 'gitlens.ai.explainCommit'
614616
| 'gitlens.ai.generateChangelog'
615617
| 'gitlens.ai.generateCommitMessage'
616618
| 'gitlens.applyPatchFromClipboard'

src/git/actions/commit.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { env, Range, Uri, window, workspace } from 'vscode';
33
import type { DiffWithCommandArgs } from '../../commands/diffWith';
44
import type { DiffWithPreviousCommandArgs } from '../../commands/diffWithPrevious';
55
import type { DiffWithWorkingCommandArgs } from '../../commands/diffWithWorking';
6+
import type { ExplainCommitCommandArgs } from '../../commands/explainCommit';
67
import type { OpenFileOnRemoteCommandArgs } from '../../commands/openFileOnRemote';
78
import type { OpenOnlyChangedFilesCommandArgs } from '../../commands/openOnlyChangedFiles';
89
import type { OpenWorkingFileCommandArgs } from '../../commands/openWorkingFile';
@@ -11,6 +12,7 @@ import type { ShowQuickCommitFileCommandArgs } from '../../commands/showQuickCom
1112
import type { FileAnnotationType } from '../../config';
1213
import { GlyphChars } from '../../constants';
1314
import { Container } from '../../container';
15+
import type { AIExplainSource } from '../../plus/ai/aiProviderService';
1416
import { showRevisionFilesPicker } from '../../quickpicks/revisionFilesPicker';
1517
import { executeCommand, executeCoreGitCommand, executeEditorCommand } from '../../system/-webview/command';
1618
import { configuration } from '../../system/-webview/configuration';
@@ -751,6 +753,17 @@ export async function showInCommitGraph(
751753
}));
752754
}
753755

756+
export async function explainCommit(
757+
commit: GitRevisionReference | GitCommit,
758+
options?: { source?: AIExplainSource },
759+
): Promise<void> {
760+
void (await executeCommand<ExplainCommitCommandArgs>('gitlens.ai.explainCommit', {
761+
repoPath: commit.repoPath,
762+
ref: commit.ref,
763+
source: options?.source,
764+
}));
765+
}
766+
754767
export async function openOnlyChangedFiles(container: Container, commit: GitCommit): Promise<void>;
755768
export async function openOnlyChangedFiles(container: Container, files: GitFile[]): Promise<void>;
756769
export async function openOnlyChangedFiles(container: Container, commitOrFiles: GitCommit | GitFile[]): Promise<void> {

src/quickpicks/items/commits.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,21 @@ export class CommitOpenInGraphCommandQuickPickItem extends CommandQuickPickItem
290290
}
291291
}
292292

293+
export class CommitExplainCommandQuickPickItem extends CommandQuickPickItem {
294+
constructor(private readonly commit: GitCommit) {
295+
super('Explain Changes', new ThemeIcon('sparkle'));
296+
}
297+
298+
override execute(_options: { preserveFocus?: boolean; preview?: boolean }): Promise<void> {
299+
return CommitActions.explainCommit(this.commit, {
300+
source: {
301+
source: 'commandPalette',
302+
type: 'commit',
303+
},
304+
});
305+
}
306+
}
307+
293308
export class CommitOpenFilesCommandQuickPickItem extends CommandQuickPickItem {
294309
constructor(private readonly commit: GitCommit) {
295310
super('Open Files', new ThemeIcon('files'));

0 commit comments

Comments
 (0)