Skip to content

Commit d142f2b

Browse files
committed
Add explain branch (wip)
1 parent 81c1e5d commit d142f2b

File tree

7 files changed

+283
-3
lines changed

7 files changed

+283
-3
lines changed

contributions.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
]
2424
}
2525
},
26+
"gitlens.ai.explainBranch": {
27+
"label": "Explain Branch (Preview)...",
28+
"commandPalette": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled"
29+
},
2630
"gitlens.ai.explainCommit": {
2731
"label": "Explain Commit...",
2832
"commandPalette": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.enabled",

docs/telemetry-events.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
115115
```typescript
116116
{
117-
'changeType': 'wip' | 'stash' | 'commit' | 'draft-stash' | 'draft-patch' | 'draft-suggested_pr_change',
117+
'changeType': 'wip' | 'stash' | 'commit' | 'branch' | 'draft-stash' | 'draft-patch' | 'draft-suggested_pr_change',
118118
'config.largePromptThreshold': number,
119119
'config.usedCustomInstructions': boolean,
120120
'duration': number,
@@ -2063,7 +2063,7 @@ or
20632063
'context.webview.type': string,
20642064
'period': 'all' | `${number}|D` | `${number}|M` | `${number}|Y`,
20652065
'showAllBranches': boolean,
2066-
'sliceBy': 'author' | 'branch'
2066+
'sliceBy': 'branch' | 'author'
20672067
}
20682068
```
20692069

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6074,6 +6074,11 @@
60746074
"category": "GitLens",
60756075
"icon": "$(person-add)"
60766076
},
6077+
{
6078+
"command": "gitlens.ai.explainBranch",
6079+
"title": "Explain Branch (Preview)...",
6080+
"category": "GitLens"
6081+
},
60776082
{
60786083
"command": "gitlens.ai.explainCommit",
60796084
"title": "Explain Commit...",
@@ -10409,6 +10414,10 @@
1040910414
"command": "gitlens.addAuthors",
1041010415
"when": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && !gitlens:hasVirtualFolders"
1041110416
},
10417+
{
10418+
"command": "gitlens.ai.explainBranch",
10419+
"when": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled"
10420+
},
1041210421
{
1041310422
"command": "gitlens.ai.explainCommit",
1041410423
"when": "gitlens:enabled && !gitlens:readonly && !gitlens:untrusted && gitlens:gk:organization:ai:enabled && config.gitlens.ai.enabled"

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/explainBranch';
2425
import './commands/explainCommit';
2526
import './commands/explainStash';
2627
import './commands/explainWip';

src/commands/explainBranch.ts

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import type { TextEditor, Uri } from 'vscode';
2+
import { ProgressLocation } from 'vscode';
3+
import type { Source } from '../constants.telemetry';
4+
import type { Container } from '../container';
5+
import { GitUri } from '../git/gitUri';
6+
import type { GitBranchReference } from '../git/models/reference';
7+
import { showGenericErrorMessage } from '../messages';
8+
import type { AIExplainSource } from '../plus/ai/aiProviderService';
9+
import { showComparisonPicker } from '../quickpicks/comparisonPicker';
10+
import { ReferencesQuickPickIncludes, showReferencePicker } from '../quickpicks/referencePicker';
11+
import { getBestRepositoryOrShowPicker, getRepositoryOrShowPicker } from '../quickpicks/repositoryPicker';
12+
import { command } from '../system/-webview/command';
13+
import { showMarkdownPreview } from '../system/-webview/markdown';
14+
import { Logger } from '../system/logger';
15+
import { getSettledValue, getSettledValues } from '../system/promise';
16+
import { GlCommandBase } from './commandBase';
17+
import { getCommandUri } from './commandBase.utils';
18+
import type { CommandContext } from './commandContext';
19+
20+
export interface ExplainBranchCommandArgs {
21+
repoPath?: string | Uri;
22+
ref?: string;
23+
source?: AIExplainSource;
24+
}
25+
26+
@command()
27+
export class ExplainBranchCommand extends GlCommandBase {
28+
constructor(private readonly container: Container) {
29+
super('gitlens.ai.explainBranch');
30+
}
31+
32+
protected override preExecute(context: CommandContext, args?: ExplainBranchCommandArgs): Promise<void> {
33+
return this.execute(context.editor, context.uri, args);
34+
}
35+
36+
async execute(editor?: TextEditor, uri?: Uri, args?: ExplainBranchCommandArgs): Promise<void> {
37+
uri = getCommandUri(uri, editor);
38+
39+
const gitUri = uri != null ? await GitUri.fromUri(uri) : undefined;
40+
41+
const repository = await getBestRepositoryOrShowPicker(
42+
gitUri,
43+
editor,
44+
'Explain Branch',
45+
'Choose which repository to explain a branch from',
46+
);
47+
if (repository == null) return;
48+
49+
args = { ...args };
50+
51+
try {
52+
// If no ref is provided, show a picker to select a branch
53+
if (args.ref == null) {
54+
const pick = await showReferencePicker(
55+
repository.path,
56+
'Explain Branch',
57+
'Choose a branch to explain',
58+
{
59+
include: ReferencesQuickPickIncludes.Branches,
60+
sort: { branches: { current: true } },
61+
},
62+
);
63+
if (pick?.ref == null) return;
64+
args.ref = pick.ref;
65+
}
66+
67+
// Get the branch
68+
const branch = await repository.git.branches().getBranch(args.ref);
69+
if (branch == null) {
70+
void showGenericErrorMessage('Unable to find the specified branch');
71+
return;
72+
}
73+
74+
// Get the diff between the branch and its upstream or base
75+
const diffService = repository.git.diff();
76+
if (diffService?.getDiff === undefined) {
77+
void showGenericErrorMessage('Unable to get diff service');
78+
return;
79+
}
80+
81+
const diff = await diffService.getDiff(branch.ref);
82+
if (!diff?.contents) {
83+
void showGenericErrorMessage('No changes found to explain');
84+
return;
85+
}
86+
87+
// Call the AI service to explain the changes
88+
const result = await this.container.ai.explainChanges(
89+
{
90+
diff: diff.contents,
91+
message: `Changes in branch ${branch.name}`,
92+
},
93+
args.source ?? { source: 'commandPalette', type: 'commit' },
94+
{
95+
progress: { location: ProgressLocation.Notification, title: 'Explaining branch changes...' },
96+
},
97+
);
98+
99+
// Display the result
100+
let content = `# Branch: ${branch.name}\n`;
101+
if (result != null) {
102+
content += `> Generated by ${result.model.name}\n\n----\n\n${result?.parsed.summary}\n\n${result?.parsed.body}`;
103+
} else {
104+
content += `> No changes found to explain.`;
105+
}
106+
void showMarkdownPreview(content);
107+
} catch (ex) {
108+
Logger.error(ex, 'ExplainBranchCommand', 'execute');
109+
void showGenericErrorMessage('Unable to explain branch');
110+
}
111+
}
112+
}
113+
114+
export interface ExplainBranchCommandArgs2 {
115+
repoPath: string;
116+
branch: string;
117+
source?: Source;
118+
}
119+
120+
@command()
121+
export class ExplainBranchCommand2 extends GlCommandBase {
122+
constructor(private readonly container: Container) {
123+
super('gitlens.ai.explainBranch');
124+
}
125+
126+
async execute(args?: ExplainBranchCommandArgs2): Promise<void> {
127+
let repo;
128+
if (args?.repoPath != null) {
129+
repo = this.container.git.getRepository(args.repoPath);
130+
}
131+
repo ??= await getRepositoryOrShowPicker(
132+
'Explain Branch',
133+
'Choose which repository to explain a branch from',
134+
undefined,
135+
);
136+
if (repo == null) return;
137+
138+
try {
139+
// If no ref is provided, show a picker to select a branch
140+
if (args == null) {
141+
const pick = await showReferencePicker(repo.path, 'Explain Branch', 'Choose a branch to explain', {
142+
include: ReferencesQuickPickIncludes.Branches,
143+
sort: { branches: { current: true } },
144+
});
145+
if (pick?.ref == null) return;
146+
147+
args = {
148+
repoPath: repo.path,
149+
branch: pick.ref,
150+
};
151+
}
152+
153+
// Get the branch
154+
const branch = await repo.git.branches().getBranch(args.branch);
155+
if (branch == null) {
156+
void showGenericErrorMessage('Unable to find the specified branch');
157+
return;
158+
}
159+
const headRef = branch.ref;
160+
const baseRef = branch.upstream?.name;
161+
162+
// Get the diff between the branch and its upstream or base
163+
const [diffResult, logResult] = await Promise.allSettled([
164+
repo.git.diff().getDiff?.(headRef, baseRef, { notation: '...' }),
165+
repo.git.commits().getLog(`${baseRef}..${headRef}`),
166+
]);
167+
168+
const diff = getSettledValue(diffResult);
169+
const log = getSettledValue(logResult);
170+
171+
if (!diff?.contents || !log?.commits?.size) {
172+
void showGenericErrorMessage('No changes found to explain');
173+
}
174+
} catch (ex) {
175+
Logger.error(ex, 'ExplainBranchCommand', 'execute');
176+
void showGenericErrorMessage('Unable to explain branch');
177+
}
178+
}
179+
}
180+
181+
// export interface ExplainBranchCommandArgs {
182+
// repoPath: string;
183+
// branch: GitBranchReference;
184+
// source?: Source;
185+
// }
186+
187+
// @command()
188+
// export class ExplainBranchCommand extends GlCommandBase {
189+
// constructor(private readonly container: Container) {
190+
// super('gitlens.ai.explainBranch');
191+
// }
192+
193+
// async execute(args?: ExplainBranchCommandArgs): Promise<void> {
194+
// try {
195+
// const comparisonResult = await showComparisonPicker(this.container, args?.repoPath, {
196+
// head: args?.branch,
197+
// getTitleAndPlaceholder: step => {
198+
// switch (step) {
199+
// case 1:
200+
// return {
201+
// title: 'Explain Branch',
202+
// placeholder: 'Choose a branch to explain',
203+
// };
204+
// case 2:
205+
// return {
206+
// title: `Explain Branch \u2022 Select Base to Start From`,
207+
// placeholder: 'Choose a base branch to explain from',
208+
// };
209+
// }
210+
// },
211+
// });
212+
// if (comparisonResult == null) return;
213+
214+
// const repo = this.container.git.getRepository(comparisonResult.repoPath);
215+
// if (repo == null) return;
216+
217+
// const mergeBase = await repo.git.refs().getMergeBase(comparisonResult.head.ref, comparisonResult.base.ref);
218+
219+
// const [diffResult, logResult] = await Promise.allSettled([
220+
// repo.git.diff().getDiff?.(comparisonResult.head.ref, mergeBase, { notation: '...' }),
221+
// repo.git.commits().getLog(`${mergeBase}..${comparisonResult.head.ref}`),
222+
// ]);
223+
224+
// const diff = getSettledValue(diffResult);
225+
// const log = getSettledValue(logResult);
226+
227+
// if (!diff?.contents || !log?.commits?.size) {
228+
// void showGenericErrorMessage('No changes found to explain');
229+
// return;
230+
// }
231+
232+
// const commitMessages: string[] = [];
233+
// for (const commit of [...log.commits.values()].sort((a, b) => a.date.getTime() - b.date.getTime())) {
234+
// commitMessages.push(commit.message ?? commit.summary);
235+
// }
236+
237+
// const result = await this.container.ai.explainChanges(
238+
// {
239+
// diff: diff.contents,
240+
// message: commitMessages.join('\n\n'),
241+
// },
242+
// {
243+
// source: 'commandPalette',
244+
// ...args?.source,
245+
// type: 'branch',
246+
// },
247+
// {
248+
// progress: { location: ProgressLocation.Notification, title: 'Explaining branch changes...' },
249+
// },
250+
// );
251+
252+
// // Display the result
253+
// let content = `# Branch: ${comparisonResult.head.name}\n`;
254+
// if (result != null) {
255+
// content += `> Generated by ${result.model.name}\n\n----\n\n${result?.parsed.summary}\n\n${result?.parsed.body}`;
256+
// } else {
257+
// content += `> No changes found to explain.`;
258+
// }
259+
// void showMarkdownPreview(content);
260+
// } catch (ex) {
261+
// Logger.error(ex, 'ExplainBranchCommand', 'execute');
262+
// void showGenericErrorMessage('Unable to explain branch');
263+
// }
264+
// }
265+
// }

src/constants.commands.generated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ export type ContributedCommands =
614614

615615
export type ContributedPaletteCommands =
616616
| 'gitlens.addAuthors'
617+
| 'gitlens.ai.explainBranch'
617618
| 'gitlens.ai.explainCommit'
618619
| 'gitlens.ai.explainStash'
619620
| 'gitlens.ai.explainWip'

src/constants.telemetry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ interface AIEventDataBase {
336336

337337
interface AIExplainEvent extends AIEventDataBase {
338338
type: 'change';
339-
changeType: 'wip' | 'stash' | 'commit' | `draft-${'patch' | 'stash' | 'suggested_pr_change'}`;
339+
changeType: 'wip' | 'stash' | 'commit' | 'branch' | `draft-${'patch' | 'stash' | 'suggested_pr_change'}`;
340340
}
341341

342342
export interface AIGenerateCommitEventData extends AIEventDataBase {

0 commit comments

Comments
 (0)