@@ -301,28 +301,113 @@ export namespace Snapshot {
301301 const revert = Effect . fnUntraced ( function * ( patches : Snapshot . Patch [ ] ) {
302302 return yield * locked (
303303 Effect . gen ( function * ( ) {
304+ const ops : { hash : string ; file : string ; rel : string } [ ] = [ ]
304305 const seen = new Set < string > ( )
305306 for ( const item of patches ) {
306307 for ( const file of item . files ) {
307308 if ( seen . has ( file ) ) continue
308309 seen . add ( file )
309- log . info ( "reverting" , { file, hash : item . hash } )
310- const result = yield * git ( [ ...core , ...args ( [ "checkout" , item . hash , "--" , file ] ) ] , {
310+ ops . push ( {
311+ hash : item . hash ,
312+ file,
313+ rel : path . relative ( state . worktree , file ) . replaceAll ( "\\" , "/" ) ,
314+ } )
315+ }
316+ }
317+
318+ const single = Effect . fnUntraced ( function * ( op : ( typeof ops ) [ number ] ) {
319+ log . info ( "reverting" , { file : op . file , hash : op . hash } )
320+ const result = yield * git ( [ ...core , ...args ( [ "checkout" , op . hash , "--" , op . file ] ) ] , {
321+ cwd : state . worktree ,
322+ } )
323+ if ( result . code === 0 ) return
324+ const tree = yield * git ( [ ...core , ...args ( [ "ls-tree" , op . hash , "--" , op . rel ] ) ] , {
325+ cwd : state . worktree ,
326+ } )
327+ if ( tree . code === 0 && tree . text . trim ( ) ) {
328+ log . info ( "file existed in snapshot but checkout failed, keeping" , { file : op . file , hash : op . hash } )
329+ return
330+ }
331+ log . info ( "file did not exist in snapshot, deleting" , { file : op . file , hash : op . hash } )
332+ yield * remove ( op . file )
333+ } )
334+
335+ const clash = ( a : string , b : string ) => a === b || a . startsWith ( `${ b } /` ) || b . startsWith ( `${ a } /` )
336+
337+ for ( let i = 0 ; i < ops . length ; ) {
338+ const first = ops [ i ] !
339+ const run = [ first ]
340+ let j = i + 1
341+ // Only batch adjacent files when their paths cannot affect each other.
342+ while ( j < ops . length && run . length < 100 ) {
343+ const next = ops [ j ] !
344+ if ( next . hash !== first . hash ) break
345+ if ( run . some ( ( item ) => clash ( item . rel , next . rel ) ) ) break
346+ run . push ( next )
347+ j += 1
348+ }
349+
350+ if ( run . length === 1 ) {
351+ yield * single ( first )
352+ i = j
353+ continue
354+ }
355+
356+ const tree = yield * git (
357+ [ ...core , ...args ( [ "ls-tree" , "--name-only" , first . hash , "--" , ...run . map ( ( item ) => item . rel ) ] ) ] ,
358+ {
311359 cwd : state . worktree ,
360+ } ,
361+ )
362+
363+ if ( tree . code !== 0 ) {
364+ log . info ( "batched ls-tree failed, falling back to single-file revert" , {
365+ hash : first . hash ,
366+ files : run . length ,
312367 } )
313- if ( result . code !== 0 ) {
314- const rel = path . relative ( state . worktree , file )
315- const tree = yield * git ( [ ...core , ...args ( [ "ls-tree" , item . hash , "--" , rel ] ) ] , {
368+ for ( const op of run ) {
369+ yield * single ( op )
370+ }
371+ i = j
372+ continue
373+ }
374+
375+ const have = new Set (
376+ tree . text
377+ . trim ( )
378+ . split ( "\n" )
379+ . map ( ( item ) => item . trim ( ) )
380+ . filter ( Boolean ) ,
381+ )
382+ const list = run . filter ( ( item ) => have . has ( item . rel ) )
383+ if ( list . length ) {
384+ log . info ( "reverting" , { hash : first . hash , files : list . length } )
385+ const result = yield * git (
386+ [ ...core , ...args ( [ "checkout" , first . hash , "--" , ...list . map ( ( item ) => item . file ) ] ) ] ,
387+ {
316388 cwd : state . worktree ,
389+ } ,
390+ )
391+ if ( result . code !== 0 ) {
392+ log . info ( "batched checkout failed, falling back to single-file revert" , {
393+ hash : first . hash ,
394+ files : list . length ,
317395 } )
318- if ( tree . code === 0 && tree . text . trim ( ) ) {
319- log . info ( "file existed in snapshot but checkout failed, keeping" , { file } )
320- } else {
321- log . info ( "file did not exist in snapshot, deleting" , { file } )
322- yield * remove ( file )
396+ for ( const op of run ) {
397+ yield * single ( op )
323398 }
399+ i = j
400+ continue
324401 }
325402 }
403+
404+ for ( const op of run ) {
405+ if ( have . has ( op . rel ) ) continue
406+ log . info ( "file did not exist in snapshot, deleting" , { file : op . file , hash : op . hash } )
407+ yield * remove ( op . file )
408+ }
409+
410+ i = j
326411 }
327412 } ) ,
328413 )
0 commit comments