Skip to content

Commit 1ddc7e7

Browse files
d13eamodioaxosoft-ramint
committed
Updates generate rebase into a single-pass prompt
Co-authored-by: Eric Amodio <[email protected]> Co-authored-by: Ramin Tadayon <[email protected]>
1 parent a0f5309 commit 1ddc7e7

File tree

5 files changed

+107
-133
lines changed

5 files changed

+107
-133
lines changed

src/commands/generateRebase.ts

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class GenerateCommitsCommand extends GlCommandBase {
8383
createReference(uncommitted, svc.path, { refType: 'revision' }),
8484
createReference('HEAD', svc.path, { refType: 'revision' }),
8585
args?.source ?? { source: 'commandPalette' },
86-
{ progress: { location: ProgressLocation.Notification } },
86+
{ title: 'Generate Commits', progress: { location: ProgressLocation.Notification } },
8787
);
8888
} catch (ex) {
8989
Logger.error(ex, 'GenerateCommitsCommand', 'execute');
@@ -163,18 +163,20 @@ export async function generateRebase(
163163
head: GitReference,
164164
base: GitReference,
165165
source: Source,
166-
options?: { cancellation?: CancellationToken; progress?: ProgressOptions },
166+
options?: { title?: string; cancellation?: CancellationToken; progress?: ProgressOptions },
167167
): Promise<void> {
168+
const { title, ...aiOptions } = options ?? {};
169+
168170
const repo = svc.getRepository()!;
169-
const result = await container.ai.generateRebase(repo, base.ref, head.ref, source, options);
171+
const result = await container.ai.generateRebase(repo, base.ref, head.ref, source, aiOptions);
170172
if (result == null) return;
171173

172174
try {
173175
// Extract the diff information from the reorganized commits
174176
const diffInfo = extractRebaseDiffInfo(result.commits, result.diff, result.hunkMap);
175177

176178
// Generate the markdown content that shows each commit and its diffs
177-
const markdownContent = generateRebaseMarkdown(result);
179+
const markdownContent = generateRebaseMarkdown(result, title);
178180

179181
const shas = await repo.git.patch?.createUnreachableCommitsFromPatches(base.ref, diffInfo);
180182
if (shas?.length) {
@@ -254,19 +256,32 @@ export function extractRebaseDiffInfo(
254256
/**
255257
* Formats the reorganized commits into a readable markdown document with proper git diff format
256258
*/
257-
function generateRebaseMarkdown(result: AIRebaseResult): string {
258-
let markdown = `# Rebase Commits\n\n`;
259+
function generateRebaseMarkdown(result: AIRebaseResult, title = 'Rebase Commits'): string {
260+
let markdown = `# ${title}\n\n> Generated by ${result.model.name}\n\n`;
259261

260-
const { commits, diff: originalDiff, hunkMap, explanation } = result;
262+
const { commits, diff: originalDiff, hunkMap } = result;
263+
264+
if (commits.length === 0) {
265+
markdown += `No commits generated\n\n`;
266+
return markdown;
267+
}
261268

262-
markdown += `## Explanation\n${explanation}\n\n----\n\n`;
269+
let explanations =
270+
"## Explanation\n\nOkay, here's the breakdown of the commits I'd create from the provided diff, along with explanations for each:\n\n";
263271

272+
let changes = '## Commits\n\n';
264273
for (let i = 0; i < commits.length; i++) {
265274
const commit = commits[i];
266275

267-
markdown += `## Commit ${i + 1}: ${commit.message}\n\n`;
268-
// markdown += `### Explanation\n${commit.explanation}\n\n`;
269-
markdown += `### Changes\n`;
276+
const commitTitle = `### Commit ${i + 1}: ${commit.message}`;
277+
278+
if (commit.explanation) {
279+
explanations += `${commitTitle}\n\n${commit.explanation}\n\n`;
280+
} else {
281+
explanations += `${commitTitle}\n\nNo explanation provided.\n\n`;
282+
}
283+
284+
changes += `${commitTitle}\n\n`;
270285

271286
// Group hunks by file (diff header)
272287
const fileHunks = new Map<string, string[]>();
@@ -290,25 +305,27 @@ function generateRebaseMarkdown(result: AIRebaseResult): string {
290305

291306
// Output each file with its hunks in git patch format
292307
for (const [diffHeader, hunkHeaders] of fileHunks.entries()) {
293-
markdown += '```diff\n';
294-
markdown += `${escapeMarkdownCodeBlocks(diffHeader)}\n`;
308+
changes += '```diff\n';
309+
changes += `${escapeMarkdownCodeBlocks(diffHeader)}\n`;
295310

296311
// Extract and include the actual content for each hunk from the original diff
297312
for (const hunkHeader of hunkHeaders) {
298-
// markdown += `${hunkHeader}\n`;
299313
// Find the hunk content in the original diff
300314
const hunkContent = extractHunkContent(originalDiff, diffHeader, hunkHeader);
301315
if (hunkContent) {
302-
markdown += `${escapeMarkdownCodeBlocks(hunkContent)}\n`;
316+
changes += `${escapeMarkdownCodeBlocks(hunkContent)}\n`;
303317
} else {
304-
markdown += `Unable to extract hunk content for ${hunkHeader}\n`;
318+
changes += `Unable to extract hunk content for ${hunkHeader}\n`;
305319
}
306320
}
307321

308-
markdown += '```\n\n';
322+
changes += '```\n\n';
309323
}
310324
}
311325

326+
markdown += explanations;
327+
markdown += changes;
328+
312329
// markdown += `\n\n----\n\n## Raw commits\n\n\`\`\`${escapeMarkdownCodeBlocks(JSON.stringify(commits))}\`\`\``;
313330
// markdown += `\n\n----\n\n## Original Diff\n\n\`\`\`${escapeMarkdownCodeBlocks(originalDiff)}\`\`\`\n`;
314331

src/plus/ai/aiProviderService.ts

Lines changed: 41 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ export interface AISummarizeResult extends AIResult {
9393

9494
export interface AIRebaseResult extends AIResult {
9595
readonly diff: string;
96-
readonly explanation: string;
9796
readonly hunkMap: { index: number; hunkHeader: string }[];
9897
readonly commits: {
9998
readonly message: string;
99+
readonly explanation: string;
100100
readonly hunks: { hunk: number }[];
101101
}[];
102102
}
@@ -871,82 +871,57 @@ export class AIProviderService implements Disposable {
871871
commits: [],
872872
} as unknown as AIRebaseResult;
873873

874-
const lazyDiff = lazy(() => repo.git.diff.getDiff?.(headRef, baseRef, { notation: '...' }));
875-
876-
const conversation: AIChatMessage[] = [];
877-
878-
const req1 = await this.sendRequest(
874+
const rq = await this.sendRequest(
879875
'generate-rebase',
880876
async (model, reporting, cancellation, maxInputTokens, retries) => {
881-
const diff = await lazyDiff.value;
882-
if (!diff?.contents) {
883-
throw new AINoRequestDataError('No changes found to generate a rebase from.');
884-
}
885-
if (cancellation.isCancellationRequested) throw new CancellationError();
877+
const [diffResult, logResult] = await Promise.allSettled([
878+
repo.git.diff.getDiff?.(headRef, baseRef, { notation: '...' }),
879+
repo.git.commits.getLog(`${baseRef}..${headRef}`),
880+
]);
886881

887-
result.diff = diff.contents;
882+
const diff = getSettledValue(diffResult);
883+
const log = getSettledValue(logResult);
888884

889-
const { prompt } = await this.getPrompt(
890-
'generate-rebase-explanation',
891-
model,
892-
{
893-
diff: diff.contents,
894-
context: options?.context,
895-
// instructions: configuration.get('ai.generateRebase.customInstructions'),
896-
},
897-
maxInputTokens,
898-
retries,
899-
reporting,
900-
);
901-
if (cancellation.isCancellationRequested) throw new CancellationError();
902-
903-
const messages: AIChatMessage[] = [{ role: 'user', content: prompt }];
904-
905-
conversation.push(...messages);
906-
return messages;
907-
},
908-
m => `Generating rebase (examining changes) with ${m.name}...`,
909-
source,
910-
m => ({
911-
key: 'ai/generate',
912-
data: {
913-
type: 'rebase',
914-
'model.id': m.id,
915-
'model.provider.id': m.provider.id,
916-
'model.provider.name': m.provider.name,
917-
'retry.count': 0,
918-
},
919-
}),
920-
options,
921-
);
922-
923-
conversation.push({
924-
role: 'assistant',
925-
content: req1!.content,
926-
});
927-
result.explanation = req1!.content;
928-
929-
const req2 = await this.sendRequest(
930-
'generate-rebase',
931-
async (model, reporting, cancellation, maxInputTokens, retries) => {
932-
const diff = await lazyDiff.value;
933-
if (!diff?.contents) {
885+
if (!diff?.contents || !log?.commits?.size) {
934886
throw new AINoRequestDataError('No changes found to generate a rebase from.');
935887
}
936888
if (cancellation.isCancellationRequested) throw new CancellationError();
937889

890+
result.diff = diff.contents;
891+
938892
const hunkMap: { index: number; hunkHeader: string }[] = [];
939893
let counter = 0;
894+
//const filesDiffs = await repo.git.diff().getDiffFiles!(diff.contents)!;
895+
//for (const f of filesDiffs!.files)
896+
//for (const hunk of parsedDiff.hunks) {
897+
// hunkMap.push({ index: ++counter, hunkHeader: hunk.contents.split('\n', 1)[0] });
898+
//}
899+
900+
// let hunksByNumber= '';
901+
940902
for (const hunkHeader of diff.contents.matchAll(/@@ -\d+,\d+ \+\d+,\d+ @@(.*)$/gm)) {
941903
hunkMap.push({ index: ++counter, hunkHeader: hunkHeader[0] });
942904
}
943905

944906
result.hunkMap = hunkMap;
907+
// const hunkNumber = `hunk-${counter++}`;
908+
// hunksByNumber += `${hunkNumber}: ${hunk[0]}\n`;
909+
// }
910+
911+
// const commits: { diff: string; message: string }[] = [];
912+
// for (const commit of [...log.commits.values()].sort((a, b) => a.date.getTime() - b.date.getTime())) {
913+
// const diff = await repo.git.diff().getDiff?.(commit.ref);
914+
// commits.push({ message: commit.message ?? commit.summary, diff: diff?.contents ?? '' });
915+
916+
// if (cancellation.isCancellationRequested) throw new CancellationError();
917+
// }
945918

946919
const { prompt } = await this.getPrompt(
947-
'generate-rebase-commits',
920+
'generate-rebase',
948921
model,
949922
{
923+
diff: diff.contents,
924+
// commits: JSON.stringify(commits),
950925
data: JSON.stringify(hunkMap),
951926
context: options?.context,
952927
// instructions: configuration.get('ai.generateRebase.customInstructions'),
@@ -958,7 +933,7 @@ export class AIProviderService implements Disposable {
958933
if (cancellation.isCancellationRequested) throw new CancellationError();
959934

960935
const messages: AIChatMessage[] = [{ role: 'user', content: prompt }];
961-
return [...conversation, ...messages];
936+
return messages;
962937
},
963938
m => `Generating rebase with ${m.name}...`,
964939
source,
@@ -972,21 +947,23 @@ export class AIProviderService implements Disposable {
972947
'retry.count': 0,
973948
},
974949
}),
975-
{ ...options, modelOptions: { temperature: 0.2 } },
950+
options,
976951
);
977952

978-
// if it is wrapped in markdown, we need to strip it
979-
const content = req2!.content.replace(/^\s*```json\s*/, '').replace(/\s*```$/, '');
980-
981953
try {
954+
// if it is wrapped in markdown, we need to strip it
955+
const content = rq!.content.replace(/^\s*```json\s*/, '').replace(/\s*```$/, '');
982956
// Parse the JSON content from the result
983957
result.commits = JSON.parse(content) as AIRebaseResult['commits'];
984958
} catch {
985959
debugger;
986960
throw new Error('Unable to parse rebase result');
987961
}
988962

989-
return result;
963+
return {
964+
...rq,
965+
...result,
966+
};
990967
}
991968

992969
private async sendRequest<T extends AIActionType>(

src/plus/ai/models/promptTemplates.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,14 @@ interface CreatePullRequestPromptTemplateContext {
2828
instructions?: string;
2929
}
3030

31-
interface RebaseExplanationPromptTemplateContext {
31+
interface RebasePromptTemplateContext {
3232
diff: string;
33+
data?: string;
3334
commits?: string;
3435
context?: string;
3536
instructions?: string;
3637
}
3738

38-
interface RebaseCommitsPromptTemplateContext {
39-
data: string;
40-
context?: string;
41-
instructions?: string;
42-
}
43-
4439
interface ExplainChangesPromptTemplateContext {
4540
diff: string;
4641
message: string;
@@ -58,7 +53,7 @@ export type PromptTemplateType =
5853
| 'generate-stashMessage'
5954
| 'generate-changelog'
6055
| `generate-create-${'cloudPatch' | 'codeSuggestion' | 'pullRequest'}`
61-
| `generate-rebase-${'explanation' | 'commits'}`
56+
| 'generate-rebase'
6257
| 'explain-changes';
6358

6459
type PromptTemplateVersions = '' | '_v2';
@@ -76,10 +71,8 @@ export type PromptTemplateContext<T extends PromptTemplateType> = T extends 'gen
7671
? CreatePullRequestPromptTemplateContext
7772
: T extends 'generate-changelog'
7873
? ChangelogPromptTemplateContext
79-
: T extends 'generate-rebase-explanation'
80-
? RebaseExplanationPromptTemplateContext
81-
: T extends 'generate-rebase-commits'
82-
? RebaseCommitsPromptTemplateContext
74+
: T extends 'generate-rebase'
75+
? RebasePromptTemplateContext
8376
: T extends 'explain-changes'
8477
? ExplainChangesPromptTemplateContext
8578
: never;

0 commit comments

Comments
 (0)