@@ -452,6 +452,145 @@ export class ChatSessionWorktreeService extends Disposable implements IChatSessi
452452 } ) ;
453453 }
454454
455+ async cleanupWorktreeOnArchive ( sessionId : string ) : Promise < { cleaned : boolean ; reason ?: string } > {
456+ const worktreeProperties = await this . getWorktreeProperties ( sessionId ) ;
457+ if ( ! worktreeProperties ) {
458+ return { cleaned : false , reason : 'no-worktree' } ;
459+ }
460+
461+ const worktreePath = worktreeProperties . worktreePath ;
462+
463+ // Check if the worktree directory exists
464+ try {
465+ await fs . access ( worktreePath ) ;
466+ } catch {
467+ this . logService . trace ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] Worktree path does not exist: ${ worktreePath } ` ) ;
468+ return { cleaned : false , reason : 'worktree-not-found' } ;
469+ }
470+
471+ // Get the git repository for the worktree
472+ const repository = await this . gitCommitMessageService . getRepository ( vscode . Uri . file ( worktreePath ) ) ;
473+ if ( ! repository ) {
474+ this . logService . warn ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] Unable to find repository for worktree ${ worktreePath } ` ) ;
475+ return { cleaned : false , reason : 'no-repository' } ;
476+ }
477+
478+ const hasUncommittedChanges = repository . state . workingTreeChanges . length > 0
479+ || repository . state . indexChanges . length > 0
480+ || repository . state . untrackedChanges . length > 0 ;
481+
482+ if ( hasUncommittedChanges ) {
483+ // For auto-commit sessions, commit changes before cleanup
484+ if ( worktreeProperties . autoCommit !== false ) {
485+ this . logService . trace ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] Auto-committing changes before cleanup for session ${ sessionId } ` ) ;
486+ try {
487+ await this . handleRequestCompleted ( sessionId ) ;
488+ } catch ( error ) {
489+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
490+ this . logService . error ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] Failed to auto-commit: ${ errorMessage } ` ) ;
491+ return { cleaned : false , reason : 'auto-commit-failed' } ;
492+ }
493+ } else {
494+ // Non-auto-commit sessions with uncommitted changes: skip cleanup
495+ this . logService . trace ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] Skipping cleanup for session ${ sessionId } : has uncommitted changes and auto-commit is disabled` ) ;
496+ return { cleaned : false , reason : 'uncommitted-changes' } ;
497+ }
498+ }
499+
500+ // Verify the branch exists before deleting the worktree
501+ try {
502+ const refs = await this . gitService . getRefs (
503+ vscode . Uri . file ( worktreeProperties . repositoryPath ) ,
504+ { pattern : `refs/heads/${ worktreeProperties . branchName } ` }
505+ ) ;
506+ if ( ! refs || refs . length === 0 ) {
507+ this . logService . warn ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] Branch ${ worktreeProperties . branchName } not found, skipping cleanup` ) ;
508+ return { cleaned : false , reason : 'branch-not-found' } ;
509+ }
510+ } catch ( error ) {
511+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
512+ this . logService . warn ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] Failed to verify branch: ${ errorMessage } ` ) ;
513+ return { cleaned : false , reason : 'branch-check-failed' } ;
514+ }
515+
516+ // Delete the worktree
517+ try {
518+ const parentRepository = await this . gitService . getRepository ( vscode . Uri . file ( worktreeProperties . repositoryPath ) , true ) ;
519+ if ( ! parentRepository ) {
520+ this . logService . warn ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] No parent repository found for ${ worktreeProperties . repositoryPath } ` ) ;
521+ return { cleaned : false , reason : 'no-parent-repository' } ;
522+ }
523+ await this . gitService . deleteWorktree ( parentRepository . rootUri , worktreePath ) ;
524+ this . logService . trace ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] Deleted worktree ${ worktreePath } for session ${ sessionId } ` ) ;
525+ return { cleaned : true } ;
526+ } catch ( error ) {
527+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
528+ this . logService . error ( `[ChatSessionWorktreeService][cleanupWorktreeOnArchive] Failed to delete worktree: ${ errorMessage } ` ) ;
529+ return { cleaned : false , reason : 'delete-failed' } ;
530+ }
531+ }
532+
533+ async recreateWorktreeOnUnarchive ( sessionId : string ) : Promise < { recreated : boolean ; reason ?: string } > {
534+ const worktreeProperties = await this . getWorktreeProperties ( sessionId ) ;
535+ if ( ! worktreeProperties ) {
536+ return { recreated : false , reason : 'no-worktree-properties' } ;
537+ }
538+
539+ const worktreePath = worktreeProperties . worktreePath ;
540+
541+ // Check if the worktree already exists on disk
542+ try {
543+ await fs . access ( worktreePath ) ;
544+ this . logService . trace ( `[ChatSessionWorktreeService][recreateWorktreeOnUnarchive] Worktree already exists at ${ worktreePath } ` ) ;
545+ return { recreated : false , reason : 'already-exists' } ;
546+ } catch {
547+ // Expected — worktree was cleaned up on archive
548+ }
549+
550+ // Verify the branch still exists in the parent repository
551+ try {
552+ const refs = await this . gitService . getRefs (
553+ vscode . Uri . file ( worktreeProperties . repositoryPath ) ,
554+ { pattern : `refs/heads/${ worktreeProperties . branchName } ` }
555+ ) ;
556+ if ( ! refs || refs . length === 0 ) {
557+ this . logService . warn ( `[ChatSessionWorktreeService][recreateWorktreeOnUnarchive] Branch ${ worktreeProperties . branchName } no longer exists` ) ;
558+ return { recreated : false , reason : 'branch-not-found' } ;
559+ }
560+ } catch ( error ) {
561+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
562+ this . logService . warn ( `[ChatSessionWorktreeService][recreateWorktreeOnUnarchive] Failed to verify branch: ${ errorMessage } ` ) ;
563+ return { recreated : false , reason : 'branch-check-failed' } ;
564+ }
565+
566+ // Recreate the worktree from the existing branch
567+ try {
568+ const parentRepository = await this . gitService . getRepository ( vscode . Uri . file ( worktreeProperties . repositoryPath ) , true ) ;
569+ if ( ! parentRepository ) {
570+ this . logService . warn ( `[ChatSessionWorktreeService][recreateWorktreeOnUnarchive] No parent repository found for ${ worktreeProperties . repositoryPath } ` ) ;
571+ return { recreated : false , reason : 'no-parent-repository' } ;
572+ }
573+
574+ // Use commitish (existing branch) without branch (no -b flag) to checkout the existing branch
575+ const createdPath = await this . gitService . createWorktree ( parentRepository . rootUri , {
576+ path : worktreePath ,
577+ commitish : worktreeProperties . branchName ,
578+ } ) ;
579+
580+ if ( ! createdPath ) {
581+ this . logService . error ( `[ChatSessionWorktreeService][recreateWorktreeOnUnarchive] createWorktree returned no path` ) ;
582+ return { recreated : false , reason : 'create-failed' } ;
583+ }
584+
585+ this . logService . trace ( `[ChatSessionWorktreeService][recreateWorktreeOnUnarchive] Recreated worktree at ${ createdPath } for session ${ sessionId } ` ) ;
586+ return { recreated : true } ;
587+ } catch ( error ) {
588+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
589+ this . logService . error ( `[ChatSessionWorktreeService][recreateWorktreeOnUnarchive] Failed to recreate worktree: ${ errorMessage } ` ) ;
590+ return { recreated : false , reason : 'create-failed' } ;
591+ }
592+ }
593+
455594 private async _getWorktreeChangesFromIndex ( worktreeProperties : ChatSessionWorktreeProperties ) : Promise < readonly ChatSessionWorktreeFile [ ] | undefined > {
456595 const worktreePath = vscode . Uri . file ( worktreeProperties . worktreePath ) ;
457596 const worktreeRepository = await this . gitService . getRepository ( worktreePath ) ;
0 commit comments