@@ -2542,6 +2542,12 @@ export class Cline extends EventEmitter<ClineEvents> {
25422542
25432543 console . log ( `[checkpointDiff] Changes found, showing diff approve view` , { changesLength : changes . length } )
25442544
2545+ // Reset approved files tracking at the start of a new checkpoint diff
2546+ // Only do this if we're starting a fresh diff, not if we're continuing from a previous one
2547+ if ( mode === "checkpoint" && ! commitHash ) {
2548+ DiffApproveProvider . resetApprovedFiles ( )
2549+ }
2550+
25452551 // Create a new instance of DiffApproveProvider
25462552 const provider = new DiffApproveProvider ( this . providerRef . deref ( ) ?. context . extensionUri ! )
25472553
@@ -2554,165 +2560,226 @@ export class Cline extends EventEmitter<ClineEvents> {
25542560 changesPaths : changes . map ( ( c ) => c . paths . relative ) ,
25552561 } )
25562562
2557- // Show each change in the diff approve view
2558- for ( const change of changes ) {
2559- // Create a temporary file for the old version from git
2560- const tempDir = vscode . Uri . joinPath ( this . providerRef . deref ( ) ?. context . extensionUri ! , "temp" )
2561- const oldVersionFileName = `${ path . basename ( change . paths . relative ) } .${ previousCommitHash ?. substring ( 0 , 7 ) || "original" } `
2562- const oldVersionUri = vscode . Uri . joinPath ( tempDir , oldVersionFileName )
2563+ // Filter out files that have already been approved
2564+ const pendingChanges = changes . filter (
2565+ ( change ) => ! DiffApproveProvider . isFileApproved ( change . paths . relative ) ,
2566+ )
25632567
2564- // Use actual file path for working copy
2565- const workingUri = vscode . Uri . file ( change . paths . absolute )
2568+ // Update active views count to only include pending changes
2569+ activeViews = pendingChanges . length
25662570
2567- // Ensure temp directory exists
2571+ if ( pendingChanges . length === 0 ) {
2572+ vscode . window . showInformationMessage ( "All changes have been approved!" )
2573+ return
2574+ }
2575+
2576+ console . log (
2577+ `[checkpointDiff] Showing ${ pendingChanges . length } pending changes out of ${ changes . length } total changes` ,
2578+ )
2579+
2580+ // Show each change in the diff approve view
2581+ for ( const change of pendingChanges ) {
25682582 try {
2569- await vscode . workspace . fs . createDirectory ( tempDir )
2570- } catch ( error ) {
2571- // Directory might already exist
2572- }
2583+ // Create a temporary file for the old version from git
2584+ const tempDir = vscode . Uri . joinPath ( this . providerRef . deref ( ) ?. context . extensionUri ! , "temp" )
2585+ const oldVersionFileName = `${ path . basename ( change . paths . relative ) } .${ previousCommitHash ?. substring ( 0 , 7 ) || "original" } `
2586+ const oldVersionUri = vscode . Uri . joinPath ( tempDir , oldVersionFileName )
2587+
2588+ // Use actual file path for working copy
2589+ const workingUri = vscode . Uri . file ( change . paths . absolute )
2590+
2591+ // Ensure temp directory exists
2592+ try {
2593+ await vscode . workspace . fs . createDirectory ( tempDir )
2594+ } catch ( error ) {
2595+ // Directory might already exist
2596+ console . log ( `[checkpointDiff] Temp directory may already exist: ${ error . message } ` )
2597+ }
25732598
2574- // Write old content from git to temp file
2575- await vscode . workspace . fs . writeFile ( oldVersionUri , Buffer . from ( change . content . before || "" ) )
2576-
2577- // Show diff with approval UI
2578- await provider . show (
2579- oldVersionUri ,
2580- workingUri ,
2581- async ( blockId : number , approved : boolean ) => {
2582- if ( approved ) {
2583- vscode . window . showInformationMessage (
2584- `Block ${ blockId } in ${ change . paths . relative } approved` ,
2585- )
2586- } else {
2587- // For denied blocks, revert that block in the working file
2588- const blocks = provider . findRelatedBlocks ( blockId )
2589- if ( blocks . length > 0 ) {
2590- const workingContent = await vscode . workspace . fs . readFile ( workingUri )
2591- let workingLines = workingContent . toString ( ) . split ( "\n" )
2592-
2593- // Process blocks in reverse order to handle line numbers correctly
2594- for ( const block of blocks . reverse ( ) ) {
2595- switch ( block . type ) {
2596- case "addition" :
2597- // Remove added lines
2598- workingLines . splice ( block . newStart - 1 , block . newLines . length )
2599- break
2600- case "deletion" :
2601- // Restore deleted lines at the correct position
2602- workingLines . splice ( block . oldStart - 1 , 0 , ...block . oldLines )
2603- break
2604- case "change" :
2605- // Replace changed lines with original
2606- workingLines . splice (
2607- block . newStart - 1 ,
2608- block . newLines . length ,
2609- ...block . oldLines ,
2610- )
2611- break
2599+ // Write old content from git to temp file
2600+ await vscode . workspace . fs . writeFile ( oldVersionUri , Buffer . from ( change . content . before || "" ) )
2601+
2602+ // Show diff with approval UI
2603+ await provider . show (
2604+ oldVersionUri ,
2605+ workingUri ,
2606+ async ( blockId : number , approved : boolean ) => {
2607+ if ( approved ) {
2608+ vscode . window . showInformationMessage (
2609+ `Block ${ blockId } in ${ change . paths . relative } approved` ,
2610+ )
2611+ } else {
2612+ // For denied blocks, revert that block in the working file
2613+ const blocks = provider . findRelatedBlocks ( blockId , {
2614+ panel : { } as vscode . WebviewPanel ,
2615+ pendingBlocks : new Set ( ) ,
2616+ hasCalledAllBlocksProcessed : false ,
2617+ filePath : change . paths . absolute ,
2618+ panelId : `dummy_${ Date . now ( ) } ` ,
2619+ } )
2620+ if ( blocks . length > 0 ) {
2621+ const workingContent = await vscode . workspace . fs . readFile ( workingUri )
2622+ let workingLines = workingContent . toString ( ) . split ( "\n" )
2623+
2624+ // Process blocks in reverse order to handle line numbers correctly
2625+ for ( const block of blocks . reverse ( ) ) {
2626+ switch ( block . type ) {
2627+ case "addition" :
2628+ // Remove added lines
2629+ workingLines . splice ( block . newStart - 1 , block . newLines . length )
2630+ break
2631+ case "deletion" :
2632+ // Restore deleted lines at the correct position
2633+ workingLines . splice ( block . oldStart - 1 , 0 , ...block . oldLines )
2634+ break
2635+ case "change" :
2636+ // Replace changed lines with original
2637+ workingLines . splice (
2638+ block . newStart - 1 ,
2639+ block . newLines . length ,
2640+ ...block . oldLines ,
2641+ )
2642+ break
2643+ }
26122644 }
2613- }
26142645
2615- // Write back the updated content
2616- await vscode . workspace . fs . writeFile ( workingUri , Buffer . from ( workingLines . join ( "\n" ) ) )
2646+ // Write back the updated content
2647+ await vscode . workspace . fs . writeFile (
2648+ workingUri ,
2649+ Buffer . from ( workingLines . join ( "\n" ) ) ,
2650+ )
2651+ }
2652+ vscode . window . showInformationMessage ( `Changes in ${ change . paths . relative } reverted` )
26172653 }
2618- vscode . window . showInformationMessage ( `Changes in ${ change . paths . relative } reverted` )
2619- }
2620- } ,
2621- async ( ) => {
2622- // Check if this file has already been processed to avoid duplicate callbacks
2623- if ( processedFiles . has ( change . paths . relative ) ) {
2624- console . log (
2625- `[checkpointDiff] File ${ change . paths . relative } has already been processed, ignoring duplicate callback` ,
2626- )
2627- return // Skip processing for files we've already handled
2628- }
2629-
2630- console . log (
2631- `[checkpointDiff] onAllBlocksProcessed callback started for file: ${ change . paths . relative } ` ,
2632- )
2633- try {
2634- // Clean up temp file
2635- try {
2636- await vscode . workspace . fs . delete ( oldVersionUri )
2637- } catch ( error ) {
2654+ } ,
2655+ async ( ) => {
2656+ // Check if this file has already been processed to avoid duplicate callbacks
2657+ if ( processedFiles . has ( change . paths . relative ) ) {
26382658 console . log (
2639- `[checkpointDiff] Error deleting temp file (may already be deleted): ${ error . message } ` ,
2659+ `[checkpointDiff] File ${ change . paths . relative } has already been processed, ignoring duplicate callback ` ,
26402660 )
2661+ return // Skip processing for files we've already handled
26412662 }
26422663
2643- // Mark this file as processed
2644- processedFiles . add ( change . paths . relative )
2664+ // Mark this file as approved since all blocks have been processed
2665+ DiffApproveProvider . markFileAsApproved ( change . paths . relative )
26452666
2646- // Decrement active views counter
26472667 console . log (
2648- `[checkpointDiff] Decrementing activeViews from ${ activeViews } to ${ activeViews - 1 } ` ,
2649- { file : change . paths . relative } ,
2668+ `[checkpointDiff] onAllBlocksProcessed callback started for file: ${ change . paths . relative } ` ,
26502669 )
2651- activeViews --
2652-
2653- // When all views are closed, reset the verified checkpoint
2654- console . log ( `[checkpointDiff] After decrement, activeViews=${ activeViews } ` , {
2655- file : change . paths . relative ,
2656- processedCount : processedFiles . size ,
2657- totalChanges : changes . length ,
2658- } )
2659- if ( activeViews === 0 ) {
2670+ try {
2671+ // Clean up temp file
2672+ try {
2673+ await vscode . workspace . fs . delete ( oldVersionUri )
2674+ } catch ( error ) {
2675+ console . log (
2676+ `[checkpointDiff] Error deleting temp file (may already be deleted): ${ error . message } ` ,
2677+ )
2678+ }
2679+
2680+ // Mark this file as processed
2681+ processedFiles . add ( change . paths . relative )
2682+
2683+ // Decrement active views counter
26602684 console . log (
2661- "✅✅✅ [checkpointDiff] All diff views closed, resetting verified checkpoint ✅✅✅" ,
2685+ `[checkpointDiff] Decrementing activeViews from ${ activeViews } to ${ activeViews - 1 } ` ,
2686+ { file : change . paths . relative } ,
26622687 )
2663- const service = this . getCheckpointService ( )
2664- if ( service ) {
2665- // First reset the verified checkpoint
2666- await service . resetVerifiedCheckpoint ( )
2667-
2668- // Then save a new checkpoint - this will automatically become verified since we just reset
2669- try {
2670- const result = await service . saveCheckpoint (
2671- "Saving state after diff view closed" ,
2672- )
2673- if ( result === undefined ) {
2674- // No changes detected, set HEAD as verified checkpoint
2675- console . log (
2676- "[checkpointDiff] No changes detected, setting HEAD as verified checkpoint" ,
2677- )
2678- await service . setLastVerifiedCheckpoint ( "HEAD" )
2679- }
2680- } catch ( error ) {
2681- console . error ( "[checkpointDiff] Failed to save checkpoint:" , error )
2682- // If saving fails, try to set HEAD as the verified checkpoint
2688+ activeViews --
2689+
2690+ // When all views are closed, reset the verified checkpoint
2691+ console . log ( `[checkpointDiff] After decrement, activeViews=${ activeViews } ` , {
2692+ file : change . paths . relative ,
2693+ processedCount : processedFiles . size ,
2694+ totalChanges : changes . length ,
2695+ } )
2696+ if ( activeViews === 0 ) {
2697+ console . log (
2698+ "✅✅✅ [checkpointDiff] All diff views closed, resetting verified checkpoint ✅✅✅" ,
2699+ )
2700+ const service = this . getCheckpointService ( )
2701+ if ( service ) {
2702+ // First reset the verified checkpoint
2703+ await service . resetVerifiedCheckpoint ( )
2704+
2705+ // Then save a new checkpoint - this will automatically become verified since we just reset
26832706 try {
2684- await service . setLastVerifiedCheckpoint ( "HEAD" )
2685- } catch ( setError ) {
2686- console . error (
2687- "[checkpointDiff] Failed to set HEAD as verified checkpoint:" ,
2688- setError ,
2707+ const result = await service . saveCheckpoint (
2708+ "Saving state after diff view closed" ,
26892709 )
2710+ if ( result === undefined ) {
2711+ // No changes detected, set HEAD as verified checkpoint
2712+ console . log (
2713+ "[checkpointDiff] No changes detected, setting HEAD as verified checkpoint" ,
2714+ )
2715+ await service . setLastVerifiedCheckpoint ( "HEAD" )
2716+ }
2717+ } catch ( error ) {
2718+ console . error ( "[checkpointDiff] Failed to save checkpoint:" , error )
2719+ // If saving fails, try to set HEAD as the verified checkpoint
2720+ try {
2721+ await service . setLastVerifiedCheckpoint ( "HEAD" )
2722+ } catch ( setError ) {
2723+ console . error (
2724+ "[checkpointDiff] Failed to set HEAD as verified checkpoint:" ,
2725+ setError ,
2726+ )
2727+ }
26902728 }
26912729 }
2692- }
26932730
2694- const provider = this . providerRef . deref ( )
2695- if ( provider ) {
2696- provider . log ( "[checkpointDiff] All diff views closed, verified checkpoint reset" )
2697- provider . postMessageToWebview ( { type : "currentCheckpointUpdated" , text : "" } )
2731+ const provider = this . providerRef . deref ( )
2732+ if ( provider ) {
2733+ provider . log (
2734+ "[checkpointDiff] All diff views closed, verified checkpoint reset" ,
2735+ )
2736+ provider . postMessageToWebview ( { type : "currentCheckpointUpdated" , text : "" } )
2737+ }
2738+ } else {
2739+ console . log (
2740+ `[checkpointDiff] Still waiting for ${ activeViews } more files to be processed` ,
2741+ )
26982742 }
2699- } else {
27002743 console . log (
2701- `[checkpointDiff] Still waiting for ${ activeViews } more files to be processed ` ,
2744+ `[checkpointDiff] onAllBlocksProcessed callback completed for file: ${ change . paths . relative } ` ,
27022745 )
2746+ } catch ( error ) {
2747+ console . error ( "Failed to cleanup:" , error )
2748+ }
2749+ } ,
2750+ )
2751+ } catch ( error ) {
2752+ // Log the error but continue with other files
2753+ console . error ( `[checkpointDiff] Error showing diff for ${ change . paths . relative } :` , error )
2754+ vscode . window . showErrorMessage ( `Error showing diff for ${ change . paths . relative } : ${ error . message } ` )
2755+
2756+ // Decrement active views since this one failed
2757+ activeViews --
2758+ console . log ( `[checkpointDiff] Decremented activeViews to ${ activeViews } due to error` )
2759+
2760+ // If all views have failed, we should still reset the checkpoint
2761+ if ( activeViews === 0 ) {
2762+ console . log ( `[checkpointDiff] All diff views failed, resetting verified checkpoint` )
2763+ const service = this . getCheckpointService ( )
2764+ if ( service ) {
2765+ try {
2766+ await service . resetVerifiedCheckpoint ( )
2767+ await service . setLastVerifiedCheckpoint ( "HEAD" )
2768+ } catch ( resetError ) {
2769+ console . error ( `[checkpointDiff] Error resetting checkpoint:` , resetError )
27032770 }
2704- console . log (
2705- `[checkpointDiff] onAllBlocksProcessed callback completed for file: ${ change . paths . relative } ` ,
2706- )
2707- } catch ( error ) {
2708- console . error ( "Failed to cleanup:" , error )
27092771 }
2710- } ,
2711- )
2772+ }
2773+ }
27122774 }
27132775 } catch ( err ) {
2714- this . providerRef . deref ( ) ?. log ( "[checkpointDiff] disabling checkpoints for this task" )
2715- this . enableCheckpoints = false
2776+ console . error ( "[checkpointDiff] Error in checkpoint diff:" , err )
2777+ vscode . window . showErrorMessage ( `Error showing diff: ${ err . message } ` )
2778+
2779+ // Don't close panels or reset approved files - let the user continue working
2780+
2781+ // Don't disable checkpoints completely, just log the error
2782+ this . providerRef . deref ( ) ?. log ( "[checkpointDiff] Error occurred during diff, but checkpoints remain enabled" )
27162783 }
27172784 }
27182785
0 commit comments