11import type { CancellationToken , ProgressOptions } from 'vscode' ;
2- import { ProgressLocation } from 'vscode' ;
2+ import { ProgressLocation , window } from 'vscode' ;
33import type { Source } from '../constants.telemetry' ;
44import type { Container } from '../container' ;
55import type { MarkdownContentMetadata } from '../documents/markdown' ;
66import { getMarkdownHeaderContent } from '../documents/markdown' ;
77import type { GitRepositoryService } from '../git/gitRepositoryService' ;
8- import type { GitReference } from '../git/models/reference' ;
8+ import type { GitStashCommit } from '../git/models/commit' ;
9+ import type { GitReference , GitStashReference } from '../git/models/reference' ;
910import { uncommitted } from '../git/models/revision' ;
1011import { createReference } from '../git/utils/reference.utils' ;
1112import { showGenericErrorMessage } from '../messages' ;
1213import type { AIRebaseResult } from '../plus/ai/aiProviderService' ;
1314import { showComparisonPicker } from '../quickpicks/comparisonPicker' ;
1415import { getRepositoryOrShowPicker } from '../quickpicks/repositoryPicker' ;
15- import { command } from '../system/-webview/command' ;
16+ import { command , executeCommand } from '../system/-webview/command' ;
1617import { showMarkdownPreview } from '../system/-webview/markdown' ;
1718import { Logger } from '../system/logger' ;
1819import { escapeMarkdownCodeBlocks } from '../system/markdown' ;
@@ -36,6 +37,16 @@ export interface GenerateCommitsCommandArgs {
3637 source ?: Source ;
3738}
3839
40+ export interface UndoGenerateRebaseCommandArgs {
41+ repoPath ?: string ;
42+ generatedHeadRef ?: GitReference ;
43+ previousHeadRef ?: GitReference ;
44+ generatedStashRef ?: GitStashReference ;
45+ generatedBranchName ?: string ;
46+ undoCommand ?: `gitlens.ai.generateCommits` | `gitlens.ai.generateRebase`;
47+ source ?: Source ;
48+ }
49+
3950/**
4051 * Represents a file patch with its diff header and hunk contents
4152 */
@@ -163,6 +174,158 @@ export class GenerateRebaseCommand extends GlCommandBase {
163174 }
164175}
165176
177+ @command ( )
178+ export class UndoGenerateRebaseCommand extends GlCommandBase {
179+ constructor ( private readonly container : Container ) {
180+ super ( 'gitlens.ai.undoGenerateRebase' ) ;
181+ }
182+
183+ async execute ( args ?: UndoGenerateRebaseCommandArgs ) : Promise < void > {
184+ try {
185+ if ( ! args ?. undoCommand ) {
186+ Logger . error ( undefined , 'UndoGenerateRebaseCommand' , 'execute' , 'Missing undoCommand parameter' ) ;
187+ void window . showErrorMessage ( 'Unable to undo: Missing command information' ) ;
188+ return ;
189+ }
190+
191+ if ( args . undoCommand === 'gitlens.ai.generateRebase' ) {
192+ await this . undoGenerateRebase ( args ) ;
193+ } else if ( args . undoCommand === 'gitlens.ai.generateCommits' ) {
194+ await this . undoGenerateCommits ( args ) ;
195+ } else {
196+ const unknownCommand = args . undoCommand as string ;
197+ Logger . error (
198+ undefined ,
199+ 'UndoGenerateRebaseCommand' ,
200+ 'execute' ,
201+ `Unknown undoCommand: ${ unknownCommand } ` ,
202+ ) ;
203+ void window . showErrorMessage ( `Unable to undo: Unknown command ${ unknownCommand } ` ) ;
204+ }
205+ } catch ( ex ) {
206+ Logger . error ( ex , 'UndoGenerateRebaseCommand' , 'execute' ) ;
207+ void showGenericErrorMessage ( 'Unable to undo operation' ) ;
208+ }
209+ }
210+
211+ private async undoGenerateRebase ( args : UndoGenerateRebaseCommandArgs ) : Promise < void > {
212+ // Check required parameters
213+ if ( ! args . repoPath || ! args . generatedBranchName ) {
214+ Logger . error (
215+ undefined ,
216+ 'UndoGenerateRebaseCommand' ,
217+ 'undoGenerateRebase' ,
218+ 'Missing required parameters: repoPath or generatedBranchName' ,
219+ ) ;
220+ void window . showErrorMessage ( 'Unable to undo rebase: Missing required information' ) ;
221+ return ;
222+ }
223+
224+ const svc = this . container . git . getRepositoryService ( args . repoPath ) ;
225+
226+ // Warn user and ask for confirmation
227+ const confirm = { title : 'Delete Branch' } ;
228+ const cancel = { title : 'Cancel' , isCloseAffordance : true } ;
229+ const result = await window . showWarningMessage (
230+ `This will delete the branch '${ args . generatedBranchName } '. This action cannot be undone.\n\nAre you sure you want to continue?` ,
231+ { modal : true } ,
232+ confirm ,
233+ cancel ,
234+ ) ;
235+
236+ if ( result !== confirm ) return ;
237+
238+ try {
239+ // Try to delete the branch
240+ await svc . branches . deleteLocalBranch ?.( args . generatedBranchName , { force : true } ) ;
241+ void window . showInformationMessage (
242+ `Successfully deleted branch '${ args . generatedBranchName } '. Undo completed.` ,
243+ ) ;
244+ } catch ( ex ) {
245+ Logger . error ( ex , 'UndoGenerateRebaseCommand' , 'undoGenerateRebase' ) ;
246+
247+ // Check if it's because the user is on the branch or other specific errors
248+ const errorMessage = ex instanceof Error ? ex . message : String ( ex ) ;
249+ if ( errorMessage . includes ( 'checked out' ) || errorMessage . includes ( 'current branch' ) ) {
250+ void window . showErrorMessage (
251+ `Cannot delete branch '${ args . generatedBranchName } ' because it is currently checked out.` ,
252+ ) ;
253+ } else {
254+ void window . showErrorMessage ( `Failed to delete branch '${ args . generatedBranchName } ': ${ errorMessage } ` ) ;
255+ }
256+ }
257+ }
258+
259+ private async undoGenerateCommits ( args : UndoGenerateRebaseCommandArgs ) : Promise < void > {
260+ // Check required parameters
261+ if ( ! args . repoPath || ! args . generatedHeadRef || ! args . previousHeadRef || ! args . generatedStashRef ) {
262+ Logger . error (
263+ undefined ,
264+ 'UndoGenerateRebaseCommand' ,
265+ 'undoGenerateCommits' ,
266+ 'Missing required parameters: repoPath, generatedHeadRef, previousHeadRef, or generatedStashRef' ,
267+ ) ;
268+ void window . showErrorMessage ( 'Unable to undo commits: Missing required information' ) ;
269+ return ;
270+ }
271+
272+ const svc = this . container . git . getRepositoryService ( args . repoPath ) ;
273+
274+ try {
275+ // Check if current HEAD matches the generated HEAD
276+ const log = await svc . commits . getLog ( undefined , { limit : 1 } ) ;
277+ const currentCommit = log ?. commits . values ( ) . next ( ) . value ;
278+ if ( ! currentCommit || currentCommit . sha !== args . generatedHeadRef . ref ) {
279+ void window . showErrorMessage (
280+ 'Cannot undo commits: Your HEAD reference has changed since the commits were generated. Please ensure you are on the correct commit.' ,
281+ ) ;
282+ return ;
283+ }
284+
285+ // Warn user and ask for confirmation
286+ const confirm = { title : 'Undo Commits' } ;
287+ const cancel = { title : 'Cancel' , isCloseAffordance : true } ;
288+ const result = await window . showWarningMessage (
289+ `This will reset your current branch to ${ args . previousHeadRef . ref } and restore your previous working changes. Any work done after generating commits will be lost.\n\nAre you sure you want to continue?` ,
290+ { modal : true } ,
291+ confirm ,
292+ cancel ,
293+ ) ;
294+
295+ if ( result !== confirm ) return ;
296+
297+ // Check if there are working tree changes and stash them
298+ const status = await svc . status . getStatus ( ) ;
299+ if ( status ?. files && status . files . length > 0 ) {
300+ await svc . stash ?. saveStash ( undefined , undefined , { includeUntracked : true } ) ;
301+ }
302+
303+ // Reset hard to the previous HEAD
304+ await svc . reset ( args . previousHeadRef . ref , { hard : true } ) ;
305+
306+ // Apply the generated stash
307+ try {
308+ await svc . stash ?. applyStash ( args . generatedStashRef . ref ) ;
309+ } catch ( ex ) {
310+ Logger . error ( ex , 'UndoGenerateRebaseCommand' , 'undoGenerateCommits' , 'Failed to apply stash' ) ;
311+ void window . showWarningMessage (
312+ `Reset completed, but failed to apply the original stash: ${
313+ ex instanceof Error ? ex . message : String ( ex )
314+ } `,
315+ ) ;
316+ return ;
317+ }
318+
319+ void window . showInformationMessage (
320+ 'Successfully undid the generated commits and restored your previous working changes. Undo completed.' ,
321+ ) ;
322+ } catch ( ex ) {
323+ Logger . error ( ex , 'UndoGenerateRebaseCommand' , 'undoGenerateCommits' ) ;
324+ void window . showErrorMessage ( `Failed to undo commits: ${ ex instanceof Error ? ex . message : String ( ex ) } ` ) ;
325+ }
326+ }
327+ }
328+
166329export async function generateRebase (
167330 container : Container ,
168331 svc : GitRepositoryService ,
@@ -191,18 +354,62 @@ export async function generateRebase(
191354
192355 let generateType : 'commits' | 'rebase' = 'rebase' ;
193356 let headRefSlug = head . ref ;
357+ let generatedBranchName : string | undefined ;
358+ let previousHeadRef : GitReference | undefined ;
359+ let generatedHeadRef : GitReference | undefined ;
360+ let generatedStashRef : GitStashReference | undefined ;
361+ let stashCommit : GitStashCommit | undefined ;
362+ let previousStashCommit : GitStashCommit | undefined ;
363+
194364 const shas = await repo . git . patch ?. createUnreachableCommitsFromPatches ( base . ref , diffInfo ) ;
195365 if ( shas ?. length ) {
196366 if ( head . ref === uncommitted ) {
197367 generateType = 'commits' ;
198368 headRefSlug = 'uncommitted' ;
369+
370+ // Capture the current HEAD before making changes
371+ const log = await svc . commits . getLog ( undefined , { limit : 1 } ) ;
372+ if ( log ?. commits . size ) {
373+ const currentCommit = log . commits . values ( ) . next ( ) . value ;
374+ if ( currentCommit ) {
375+ previousHeadRef = createReference ( currentCommit . sha , svc . path , { refType : 'revision' } ) ;
376+ }
377+ }
378+
379+ let stash = await svc . stash ?. getStash ( ) ;
380+ if ( stash ?. stashes . size ) {
381+ const latestStash = stash . stashes . values ( ) . next ( ) . value ;
382+ if ( latestStash ) {
383+ previousStashCommit = latestStash ;
384+ }
385+ }
386+
199387 // stash the working changes
200388 await svc . stash ?. saveStash ( undefined , undefined , { includeUntracked : true } ) ;
201- // await repo.git.checkout?.(shas[shas.length - 1]);
389+
390+ // Get the latest stash reference
391+ stash = await svc . stash ?. getStash ( ) ;
392+ if ( stash ?. stashes . size ) {
393+ stashCommit = stash . stashes . values ( ) . next ( ) . value ;
394+ if ( stashCommit ) {
395+ generatedStashRef = createReference ( stashCommit . ref , svc . path , {
396+ refType : 'stash' ,
397+ name : stashCommit . stashName ,
398+ number : stashCommit . stashNumber ,
399+ message : stashCommit . message ,
400+ stashOnRef : stashCommit . stashOnRef ,
401+ } ) ;
402+ }
403+ }
404+
202405 // reset the current branch to the new shas
203406 await svc . reset ( shas [ shas . length - 1 ] , { hard : true } ) ;
407+
408+ // Capture the new HEAD after reset
409+ generatedHeadRef = createReference ( shas [ shas . length - 1 ] , svc . path , { refType : 'revision' } ) ;
204410 } else {
205- await svc . branches . createBranch ?.( `rebase/${ head . ref } -${ Date . now ( ) } ` , shas [ shas . length - 1 ] ) ;
411+ generatedBranchName = `rebase/${ head . ref } -${ Date . now ( ) } ` ;
412+ await svc . branches . createBranch ?.( generatedBranchName , shas [ shas . length - 1 ] ) ;
206413 }
207414 }
208415
@@ -214,6 +421,40 @@ export async function generateRebase(
214421 ) ;
215422
216423 showMarkdownPreview ( documentUri ) ;
424+
425+ // Show success notification with Undo button
426+ const undoButton = { title : 'Undo' } ;
427+ const resultNotification = await window . showInformationMessage (
428+ generateType === 'commits'
429+ ? 'Successfully generated commits from your working changes.'
430+ : 'Successfully generated rebase branch.' ,
431+ undoButton ,
432+ ) ;
433+
434+ if ( resultNotification === undoButton ) {
435+ if ( generateType === 'commits' ) {
436+ // Undo GenerateCommitsCommand
437+ void executeCommand ( 'gitlens.ai.undoGenerateRebase' , {
438+ undoCommand : 'gitlens.ai.generateCommits' ,
439+ repoPath : svc . path ,
440+ generatedHeadRef : generatedHeadRef ,
441+ previousHeadRef : previousHeadRef ,
442+ generatedStashRef :
443+ stashCommit != null && stashCommit . ref !== previousStashCommit ?. ref
444+ ? generatedStashRef
445+ : undefined ,
446+ source : source ,
447+ } satisfies UndoGenerateRebaseCommandArgs ) ;
448+ } else {
449+ // Undo GenerateRebaseCommand
450+ void executeCommand ( 'gitlens.ai.undoGenerateRebase' , {
451+ undoCommand : 'gitlens.ai.generateRebase' ,
452+ repoPath : svc . path ,
453+ generatedBranchName : generatedBranchName ,
454+ source : source ,
455+ } satisfies UndoGenerateRebaseCommandArgs ) ;
456+ }
457+ }
217458 } catch ( ex ) {
218459 Logger . error ( ex , 'GenerateRebaseCommand' , 'execute' ) ;
219460 void showGenericErrorMessage ( 'Unable to parse rebase result' ) ;
0 commit comments