@@ -335,14 +335,15 @@ class Engine {
335335 { coalesceKey, idleMs, overwrote = { } } : ClaimOptions ,
336336 ) : Promise < { id : string } | null > {
337337 await this . sweep ( ) ;
338- const taken = this . takeClaimed ( who , paths , overwrote ) ;
338+ const { taken, split } = this . takeClaimed ( who , paths , overwrote ) ;
339339 if ( ! taken . length ) {
340340 // A claim under another key still ends the held one.
341341 if ( coalesceKey !== this . claimed ?. key ) await this . commitClaim ( ) ;
342342 return null ;
343343 }
344- // What happened outside before this write is older than it, so it is logged first.
344+ // What happened outside, or in a window cut by this claim, before this write is older than it: logged first.
345345 await this . commitOutside ( ) ;
346+ for ( const window of split ) await this . commitSoFar ( window ) ;
346347 const group = await this . claimGroup ( who , label , coalesceKey ) ;
347348 for ( const change of taken ) addChange ( group , change . path , change . before , change . after ) ;
348349 if ( coalesceKey ) return this . holdClaim ( group , coalesceKey , idleMs ) ;
@@ -364,13 +365,13 @@ class Engine {
364365
365366 /**
366367 * Removes and returns the uncommitted changes to `paths` (project-relative or absolute) filed outside or in another
367- * writer's open window, each cut at the version `who` overwrote.
368+ * writer's open window, each cut at the version `who` overwrote; `split` names the windows that keep a part .
368369 */
369370 takeClaimed (
370371 who : HistoryWho ,
371372 paths : readonly string [ ] ,
372373 overwrote : Readonly < Record < string , string > > ,
373- ) : HistoryFileChange [ ] {
374+ ) : { taken : HistoryFileChange [ ] ; split : Group [ ] } {
374375 const wanted = new Set ( paths . map ( ( path ) => this . logPath ( path ) ) ) ;
375376 const at = new Map (
376377 Object . entries ( overwrote ) . map ( ( [ path , version ] ) => [
@@ -381,17 +382,34 @@ class Engine {
381382 const others = this . windows . filter ( ( open ) => ! sameWho ( open . who , who ) ) ;
382383 const groups = this . outside ? [ this . outside , ...others ] : others ;
383384 const taken : HistoryFileChange [ ] = [ ] ;
385+ const split = new Set < Group > ( ) ;
384386 for ( const group of groups ) {
385387 for ( const change of [ ...group . changes . values ( ) ] ) {
386388 if ( ! wanted . has ( change . path ) ) continue ;
387- const cut = at . get ( change . path ) ;
388- const [ kept , claimed ] = splitAt ( change , cut , cut !== undefined && this . blobs . has ( cut ) ) ;
389- group . changes . delete ( change . path ) ;
390- if ( kept ) group . changes . set ( kept . path , kept ) ;
389+ const [ kept , claimed ] = this . cutOut ( group , change , at . get ( change . path ) ) ;
391390 if ( claimed ) taken . push ( claimed ) ;
391+ if ( kept && claimed && group !== this . outside ) split . add ( group ) ;
392392 }
393393 }
394- return taken ;
394+ return { taken, split : [ ...split ] } ;
395+ }
396+
397+ /** Cuts `change` in `group` at `cut`: the part before stays in the group; both parts are returned. */
398+ cutOut ( group : Group , change : HistoryFileChange , cut : string | undefined ) {
399+ const [ kept , claimed ] = splitAt ( change , cut , cut !== undefined && this . blobs . has ( cut ) ) ;
400+ group . changes . delete ( change . path ) ;
401+ if ( kept ) group . changes . set ( kept . path , kept ) ;
402+ return [ kept , claimed ] as const ;
403+ }
404+
405+ /**
406+ * Commits a window's writes so far as an entry of its own and keeps the window open. A window holds one change per
407+ * file, so without this the writer's next write to a file cut by a claim would span the claimer's edit.
408+ */
409+ async commitSoFar ( window : Group ) : Promise < void > {
410+ window . entry = await this . commit ( { ...window , id : randomUUID ( ) } ) ;
411+ window . changes = new Map ( ) ;
412+ window . startedAt = this . now ( ) ;
395413 }
396414
397415 holdClaim (
@@ -444,9 +462,9 @@ class Engine {
444462
445463 async commit ( group : Group , extra : Partial < HistoryEntry > = { } ) : Promise < HistoryEntry | null > {
446464 if ( ! group . changes . size ) return null ;
447- const { changes , ... rest } = group ;
465+ const { id , who , label , startedAt , changes } = group ;
448466 const files = [ ...changes . values ( ) ] . sort ( ( a , b ) => a . path . localeCompare ( b . path ) ) ;
449- const entry : HistoryEntry = { ... rest , endedAt : this . now ( ) , files, ...extra } ;
467+ const entry : HistoryEntry = { id , who , label , startedAt , endedAt : this . now ( ) , files, ...extra } ;
450468 this . log . entries . push ( entry ) ;
451469 try {
452470 saveRecord ( this . logFile , this . log , { type : "entry" , entry } ) ;
@@ -530,7 +548,8 @@ class Engine {
530548 if ( ! this . windows . includes ( window ) ) return window . entry ?? null ;
531549 clearTimeout ( window . idleTimer ) ;
532550 this . windows = this . windows . filter ( ( open ) => open !== window ) ;
533- window . entry = await this . commit ( window ) ;
551+ // A window cut by a claim may have nothing after the cut: it became the entry committed then.
552+ window . entry = ( await this . commit ( window ) ) ?? window . entry ?? null ;
534553 return window . entry ;
535554 }
536555
0 commit comments