@@ -804,6 +804,121 @@ test("a throwing wrapped route settles the mutex without poisoning the chain (is
804804 } ) ;
805805} ) ;
806806
807+ test ( "/api/reset unstages every reviewed file in one batched restore and clears the review (issue 13)" , async ( ) => {
808+ const root = await mkdtemp ( path . join ( tmpdir ( ) , "galley-reset-" ) ) ;
809+ const oldHome = process . env . HOME ;
810+ process . env . HOME = root ;
811+ const g = ( args : string [ ] ) => execFileSync ( "git" , args , { cwd : root } ) . toString ( ) ;
812+ g ( [ "init" , "-q" ] ) ;
813+ g ( [ "config" , "user.email" , "t@t.co" ] ) ;
814+ g ( [ "config" , "user.name" , "tester" ] ) ;
815+ await writeFile ( path . join ( root , "a.ts" ) , "one\n" ) ;
816+ await writeFile ( path . join ( root , "b.ts" ) , "two\n" ) ;
817+ g ( [ "add" , "." ] ) ;
818+ g ( [ "commit" , "-qm" , "init" ] ) ;
819+ // Change both files and stage them, so the index carries two paths for the reset to restore.
820+ await writeFile ( path . join ( root , "a.ts" ) , "one CHANGED\n" ) ;
821+ await writeFile ( path . join ( root , "b.ts" ) , "two CHANGED\n" ) ;
822+ const st = await buildReviewState ( root , { session : "s" } ) ;
823+ assert . ok ( st , "built a review state for the working diff" ) ;
824+ g ( [ "add" , "." ] ) ;
825+ assert . deepEqual (
826+ g ( [ "diff" , "--cached" , "--name-only" ] ) . trim ( ) . split ( "\n" ) . sort ( ) ,
827+ [ "a.ts" , "b.ts" ] ,
828+ "both files staged before reset" ,
829+ ) ;
830+ st ! . comments . push ( {
831+ id : "c1" ,
832+ path : "a.ts" ,
833+ side : "additions" ,
834+ lineNumber : 1 ,
835+ body : "x" ,
836+ createdAt : "t" ,
837+ updatedAt : "t" ,
838+ status : "open" ,
839+ role : "user" ,
840+ } ) ;
841+ st ! . decisions = [
842+ {
843+ key : "a.ts:k" ,
844+ status : "accepted" ,
845+ path : "a.ts" ,
846+ lineNumber : 1 ,
847+ side : "additions" ,
848+ title : "t" ,
849+ } ,
850+ ] ;
851+ const handle = await startServer ( { state : st ! , open : false , idleTimeoutMs : 0 } ) ;
852+ try {
853+ const res = await fetch ( `${ handle . url } api/reset` , { method : "POST" } ) ;
854+ assert . equal ( res . status , 200 ) ;
855+ // A single batched `git restore --staged -- a.ts b.ts` cleared the index for BOTH files.
856+ assert . equal (
857+ g ( [ "diff" , "--cached" , "--name-only" ] ) . trim ( ) ,
858+ "" ,
859+ "index restored for every file" ,
860+ ) ;
861+ // …and the reviewer-owned slice is wiped.
862+ assert . deepEqual ( st ! . comments , [ ] ) ;
863+ assert . deepEqual ( st ! . decisions , [ ] ) ;
864+ } finally {
865+ handle . server . close ( ) ;
866+ process . env . HOME = oldHome ;
867+ await rm ( root , { recursive : true , force : true } ) ;
868+ }
869+ } ) ;
870+
871+ test ( "/api/reset in pr mode clears the review without touching the git index (issue 13)" , async ( ) => {
872+ const root = await mkdtemp ( path . join ( tmpdir ( ) , "galley-reset-pr-" ) ) ;
873+ const oldHome = process . env . HOME ;
874+ process . env . HOME = root ;
875+ const g = ( args : string [ ] ) => execFileSync ( "git" , args , { cwd : root } ) . toString ( ) ;
876+ g ( [ "init" , "-q" ] ) ;
877+ g ( [ "config" , "user.email" , "t@t.co" ] ) ;
878+ g ( [ "config" , "user.name" , "tester" ] ) ;
879+ await writeFile ( path . join ( root , "a.ts" ) , "one\n" ) ;
880+ g ( [ "add" , "." ] ) ;
881+ g ( [ "commit" , "-qm" , "init" ] ) ;
882+ // A staged change sitting in the index — if reset spawned git in pr mode, it would vanish.
883+ await writeFile ( path . join ( root , "a.ts" ) , "one CHANGED\n" ) ;
884+ g ( [ "add" , "." ] ) ;
885+ const st : ReviewState = {
886+ ...state ( root ) ,
887+ mode : "pr" ,
888+ comments : [
889+ {
890+ id : "c1" ,
891+ path : "a.ts" ,
892+ side : "additions" ,
893+ lineNumber : 1 ,
894+ body : "x" ,
895+ createdAt : "t" ,
896+ updatedAt : "t" ,
897+ status : "open" ,
898+ role : "user" ,
899+ } ,
900+ ] ,
901+ } ;
902+ const handle = await startServer ( { state : st , open : false , idleTimeoutMs : 0 } ) ;
903+ try {
904+ const res = await fetch ( `${ handle . url } api/reset` , { method : "POST" } ) ;
905+ assert . equal ( res . status , 200 ) ;
906+ // PR mode has no working-tree index to restore — the staged change is left exactly as it was
907+ // (staging is disabled in pr mode), proving the route never spawned git here.
908+ assert . equal (
909+ g ( [ "diff" , "--cached" , "--name-only" ] ) . trim ( ) ,
910+ "a.ts" ,
911+ "index untouched in pr mode" ,
912+ ) ;
913+ // The reviewer-owned slice is still cleared, git or no git.
914+ assert . deepEqual ( st . comments , [ ] ) ;
915+ } finally {
916+ handle . server . close ( ) ;
917+ process . env . HOME = oldHome ;
918+ await rm ( root , { recursive : true , force : true } ) ;
919+ }
920+ } ) ;
921+
807922test ( "settings API round-trips editorCommand" , async ( ) => {
808923 await withServer ( async ( handle ) => {
809924 await fetch ( `${ handle . url } api/settings` , {
0 commit comments