@@ -799,5 +799,159 @@ describeIntegration("Workspace deletion integration tests", () => {
799799 } ,
800800 TEST_TIMEOUT_SSH_MS
801801 ) ;
802+
803+ test . concurrent (
804+ "should allow deletion of squash-merged branches without force flag" ,
805+ async ( ) => {
806+ const env = await createTestEnvironment ( ) ;
807+ const tempGitRepo = await createTempGitRepo ( ) ;
808+
809+ try {
810+ const branchName = generateBranchName ( "squash-merge-test" ) ;
811+ const runtimeConfig = getRuntimeConfig ( branchName ) ;
812+ const { workspaceId } = await createWorkspaceWithInit (
813+ env ,
814+ tempGitRepo ,
815+ branchName ,
816+ runtimeConfig ,
817+ true , // waitForInit
818+ true // isSSH
819+ ) ;
820+
821+ // Configure git for committing
822+ await executeBash ( env , workspaceId , 'git config user.email "test@example.com"' ) ;
823+ await executeBash ( env , workspaceId , 'git config user.name "Test User"' ) ;
824+
825+ // Set origin to be the tempGitRepo (local bare-ish repo)
826+ // This gives us a real origin to simulate squash-merge against
827+ await executeBash (
828+ env ,
829+ workspaceId ,
830+ `git remote set-url origin "${ tempGitRepo } " || git remote add origin "${ tempGitRepo } "`
831+ ) ;
832+
833+ // Create feature commits on the branch
834+ await executeBash ( env , workspaceId , 'echo "feature1" > feature.txt' ) ;
835+ await executeBash ( env , workspaceId , "git add feature.txt" ) ;
836+ await executeBash ( env , workspaceId , 'git commit -m "Feature commit 1"' ) ;
837+
838+ await executeBash ( env , workspaceId , 'echo "feature2" >> feature.txt' ) ;
839+ await executeBash ( env , workspaceId , "git add feature.txt" ) ;
840+ await executeBash ( env , workspaceId , 'git commit -m "Feature commit 2"' ) ;
841+
842+ // Get the feature branch's final file content
843+ const featureContent = await executeBash ( env , workspaceId , "cat feature.txt" ) ;
844+
845+ // Now simulate squash-merge: add the same content to main in the origin
846+ // First, check out main in the temp repo and add the squash commit
847+ using checkoutProc = execAsync (
848+ `cd "${ tempGitRepo } " && git checkout main 2>/dev/null || git checkout -b main`
849+ ) ;
850+ await checkoutProc . result ;
851+
852+ // Create the squash commit with identical content
853+ using squashProc = execAsync (
854+ `cd "${ tempGitRepo } " && echo "${ featureContent . output . trim ( ) } " > feature.txt && git add feature.txt && git commit -m "Squash: Feature commits"`
855+ ) ;
856+ await squashProc . result ;
857+
858+ // Fetch the updated origin in the workspace
859+ await executeBash ( env , workspaceId , "git fetch origin" ) ;
860+
861+ // Verify we have unpushed commits (branch commits are not ancestors of origin/main)
862+ const logResult = await executeBash (
863+ env ,
864+ workspaceId ,
865+ "git log --branches --not --remotes --oneline"
866+ ) ;
867+ // Should show commits since our branch commits != squash commit SHA
868+ expect ( logResult . output . trim ( ) ) . not . toBe ( "" ) ;
869+
870+ // Now attempt deletion without force - should succeed because content matches
871+ const deleteResult = await env . mockIpcRenderer . invoke (
872+ IPC_CHANNELS . WORKSPACE_REMOVE ,
873+ workspaceId
874+ ) ;
875+
876+ // Should succeed - squash-merge detection should recognize content is in main
877+ expect ( deleteResult . success ) . toBe ( true ) ;
878+
879+ // Verify workspace was removed from config
880+ const config = env . config . loadConfigOrDefault ( ) ;
881+ const project = config . projects . get ( tempGitRepo ) ;
882+ if ( project ) {
883+ const stillInConfig = project . workspaces . some ( ( w ) => w . id === workspaceId ) ;
884+ expect ( stillInConfig ) . toBe ( false ) ;
885+ }
886+ } finally {
887+ await cleanupTestEnvironment ( env ) ;
888+ await cleanupTempGitRepo ( tempGitRepo ) ;
889+ }
890+ } ,
891+ TEST_TIMEOUT_SSH_MS
892+ ) ;
893+
894+ test . concurrent (
895+ "should block deletion when branch has genuinely unmerged content" ,
896+ async ( ) => {
897+ const env = await createTestEnvironment ( ) ;
898+ const tempGitRepo = await createTempGitRepo ( ) ;
899+
900+ try {
901+ const branchName = generateBranchName ( "unmerged-content-test" ) ;
902+ const runtimeConfig = getRuntimeConfig ( branchName ) ;
903+ const { workspaceId } = await createWorkspaceWithInit (
904+ env ,
905+ tempGitRepo ,
906+ branchName ,
907+ runtimeConfig ,
908+ true , // waitForInit
909+ true // isSSH
910+ ) ;
911+
912+ // Configure git for committing
913+ await executeBash ( env , workspaceId , 'git config user.email "test@example.com"' ) ;
914+ await executeBash ( env , workspaceId , 'git config user.name "Test User"' ) ;
915+
916+ // Set origin to be the tempGitRepo
917+ await executeBash (
918+ env ,
919+ workspaceId ,
920+ `git remote set-url origin "${ tempGitRepo } " || git remote add origin "${ tempGitRepo } "`
921+ ) ;
922+
923+ // Create feature commits with unique content
924+ await executeBash ( env , workspaceId , 'echo "unique-unmerged-content" > unique.txt' ) ;
925+ await executeBash ( env , workspaceId , "git add unique.txt" ) ;
926+ await executeBash ( env , workspaceId , 'git commit -m "Unique commit"' ) ;
927+
928+ // Fetch origin (main doesn't have our content)
929+ await executeBash ( env , workspaceId , "git fetch origin" ) ;
930+
931+ // Attempt deletion without force - should fail because content differs
932+ const deleteResult = await env . mockIpcRenderer . invoke (
933+ IPC_CHANNELS . WORKSPACE_REMOVE ,
934+ workspaceId
935+ ) ;
936+
937+ // Should fail - genuinely unmerged content
938+ expect ( deleteResult . success ) . toBe ( false ) ;
939+ expect ( deleteResult . error ) . toMatch ( / u n p u s h e d | c h a n g e s / i) ;
940+
941+ // Verify workspace still exists
942+ const stillExists = await workspaceExists ( env , workspaceId ) ;
943+ expect ( stillExists ) . toBe ( true ) ;
944+
945+ // Cleanup: force delete
946+ await env . mockIpcRenderer . invoke ( IPC_CHANNELS . WORKSPACE_REMOVE , workspaceId , {
947+ force : true ,
948+ } ) ;
949+ } finally {
950+ await cleanupTestEnvironment ( env ) ;
951+ await cleanupTempGitRepo ( tempGitRepo ) ;
952+ }
953+ } ,
954+ TEST_TIMEOUT_SSH_MS
955+ ) ;
802956 } ) ;
803957} ) ;
0 commit comments