|
| 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 | +// } |
0 commit comments