@@ -45,13 +45,14 @@ type mountFailure struct {
4545
4646type repoRuntime struct {
4747 cfg model.RepoConfig
48+ ctx context.Context
49+ cancel context.CancelFunc
4850 snapshot * snapshot.Store
4951 overlay * overlay.Store
5052 hydrator * hydrator.Service
5153 resolver * fusefs.Resolver
5254 mfs fusefs.MountedFS
5355 state model.RepoRuntimeState
54- stop chan struct {}
5556}
5657
5758type aheadBehind struct {
@@ -309,53 +310,68 @@ func (s *Service) Unmount(ctx context.Context, name string) error {
309310// start a FUSE mount or any background goroutines, so it's safe to call from
310311// short-lived CLI commands like add-repo.
311312func (s * Service ) prepareRepo (ctx context.Context , cfg model.RepoConfig ) error {
312- if err := os .MkdirAll (cfg .MountPath , 0o755 ); err != nil {
313- return err
314- }
315- if err := s .git .CloneBlobless (ctx , cfg ); err != nil {
316- return err
317- }
318- snap , err := snapshot .New (ctx , cfg .MetaDBPath )
313+ snap , _ , _ , _ , err := s .ensurePreparedRepo (ctx , cfg )
319314 if err != nil {
320315 return err
321316 }
322317 defer snap .Close ()
323- _ , err = s .publishHeadSnapshot (ctx , cfg , snap )
324- return err
318+ return nil
325319}
326320
327- // mountRepo opens all stores, starts the FUSE server, watcher, and refresh
328- // loop. Called by the daemon's Start for each registered repo.
329- func (s * Service ) mountRepo (ctx context.Context , cfg model.RepoConfig ) error {
321+ // ensurePreparedRepo makes sure the repo is cloned and has an initial snapshot.
322+ // The returned snapshot store remains open for callers that need to continue
323+ // into runtime startup.
324+ func (s * Service ) ensurePreparedRepo (ctx context.Context , cfg model.RepoConfig ) (* snapshot.Store , string , string , int64 , error ) {
330325 if err := os .MkdirAll (cfg .MountPath , 0o755 ); err != nil {
331- return err
326+ return nil , "" , "" , 0 , err
332327 }
333- // Clone if not already present (idempotent)
334328 if err := s .git .CloneBlobless (ctx , cfg ); err != nil {
335- return err
329+ return nil , "" , "" , 0 , err
336330 }
337331 headOID , headRef , err := s .git .ResolveHEAD (ctx , cfg )
338332 if err != nil {
339- return err
333+ return nil , "" , "" , 0 , err
340334 }
341335 snap , err := snapshot .New (ctx , cfg .MetaDBPath )
342336 if err != nil {
343- return err
337+ return nil , "" , "" , 0 , err
344338 }
345- gen , err := snap .CurrentGeneration (ctx )
346- if err != nil || gen == 0 {
347- var bErr error
348- gen , _ , bErr = s .publishSnapshot (ctx , cfg , snap , headOID , headRef )
349- if bErr != nil {
339+ storedOID , storedRef , gen , err := snap .ReadState (ctx )
340+ if err != nil || gen == 0 || storedOID != headOID || storedRef != headRef {
341+ gen , _ , err = s .publishSnapshot (ctx , cfg , snap , headOID , headRef )
342+ if err != nil {
350343 snap .Close ()
351- return bErr
344+ return nil , "" , "" , 0 , err
352345 }
353346 }
347+ return snap , headOID , headRef , gen , nil
348+ }
349+
350+ // mountRepo opens all stores, starts the FUSE server, watcher, and refresh
351+ // loop. Called by the daemon's Start for each registered repo.
352+ func (s * Service ) mountRepo (ctx context.Context , cfg model.RepoConfig ) error {
353+ snap , headOID , headRef , gen , err := s .ensurePreparedRepo (ctx , cfg )
354+ if err != nil {
355+ return err
356+ }
354357 ov , err := overlay .New (ctx , cfg )
355358 if err != nil {
356359 snap .Close ()
357360 return err
358361 }
362+ baseLookup := func (path string ) (model.BaseNode , bool ) {
363+ return snap .GetNode (gen , path )
364+ }
365+ if err := ov .Reconcile (ctx , baseLookup ); err != nil {
366+ ov .Close ()
367+ snap .Close ()
368+ return err
369+ }
370+ if err := s .git .ReadTreeHEAD (ctx , cfg ); err != nil {
371+ ov .Close ()
372+ snap .Close ()
373+ return err
374+ }
359375 h := hydrator .New (s .git )
360376
361377 resolver := & fusefs.Resolver {Snapshot : snap , Overlay : ov }
@@ -378,18 +394,20 @@ func (s *Service) mountRepo(ctx context.Context, cfg model.RepoConfig) error {
378394 s .logger .Error ("fuse mount failed, running without FUSE" , "repo" , cfg .Name , "error" , err )
379395 mfs = nil
380396 }
397+ runtimeCtx , cancel := context .WithCancel (ctx )
381398
382399 rt := & repoRuntime {
383400 cfg : cfg ,
401+ ctx : runtimeCtx ,
402+ cancel : cancel ,
384403 snapshot : snap ,
385404 overlay : ov ,
386405 hydrator : h ,
387406 resolver : resolver ,
388407 mfs : mfs ,
389408 state : newRuntimeState (cfg .ID , headOID , headRef , gen ),
390- stop : make (chan struct {}),
391409 }
392- s .startRuntime (ctx , rt )
410+ s .startRuntime (rt )
393411
394412 return nil
395413}
@@ -402,8 +420,18 @@ func (s *Service) onHEADChanged(ctx context.Context, rt *repoRuntime) {
402420 }
403421 s .mu .Lock ()
404422 prevOID := rt .state .CurrentHEADOID
423+ prevRef := rt .state .CurrentHEADRef
405424 s .mu .Unlock ()
406425 if oid == prevOID {
426+ if ref == prevRef {
427+ return
428+ }
429+ if err := rt .snapshot .UpdateHEADRef (ctx , ref ); err != nil {
430+ s .logger .Warn ("snapshot head_ref update failed" , "repo" , rt .cfg .Name , "error" , err )
431+ }
432+ s .mu .Lock ()
433+ rt .state .CurrentHEADRef = ref
434+ s .mu .Unlock ()
407435 return
408436 }
409437 gen , phase , err := s .publishSnapshot (ctx , rt .cfg , rt .snapshot , oid , ref )
@@ -444,10 +472,10 @@ func (s *Service) refreshLoop(rt *repoRuntime) {
444472 defer ticker .Stop ()
445473 for {
446474 select {
447- case <- rt .stop :
475+ case <- rt .ctx . Done () :
448476 return
449477 case <- ticker .C :
450- ctx , cancel := context .WithTimeout (context . Background () , 30 * time .Second )
478+ ctx , cancel := context .WithTimeout (rt . ctx , 30 * time .Second )
451479 err := s .git .Fetch (ctx , rt .cfg )
452480 if err != nil {
453481 s .mu .Lock ()
@@ -510,15 +538,6 @@ func (s *Service) readPersistedStatus(ctx context.Context, cfg model.RepoConfig)
510538 return st
511539}
512540
513- func (s * Service ) publishHeadSnapshot (ctx context.Context , cfg model.RepoConfig , snap * snapshot.Store ) (int64 , error ) {
514- oid , ref , err := s .git .ResolveHEAD (ctx , cfg )
515- if err != nil {
516- return 0 , err
517- }
518- gen , _ , err := s .publishSnapshot (ctx , cfg , snap , oid , ref )
519- return gen , err
520- }
521-
522541func (s * Service ) publishSnapshot (ctx context.Context , cfg model.RepoConfig , snap * snapshot.Store , oid string , ref string ) (int64 , string , error ) {
523542 nodes , err := s .git .BuildTreeIndex (ctx , cfg , oid )
524543 if err != nil {
@@ -547,23 +566,21 @@ func (s *Service) fetchState(ctx context.Context, cfg model.RepoConfig) (aheadBe
547566 return aheadBehind {ahead : ahead , behind : behind , diverged : diverged }, nil
548567}
549568
550- func (s * Service ) startRuntime (ctx context. Context , rt * repoRuntime ) {
569+ func (s * Service ) startRuntime (rt * repoRuntime ) {
551570 s .mu .Lock ()
552571 s .running [rt .cfg .ID ] = rt
553572 s .mu .Unlock ()
554573
555574 go s .refreshLoop (rt )
556575
557576 w := watcher .New (500 * time .Millisecond )
558- go w .Watch (ctx , rt .cfg .GitDir , func (sig watcher.Signal ) {
559- if sig .HEADChanged {
560- s .onHEADChanged (ctx , rt )
561- }
577+ go w .Watch (rt .ctx , rt .cfg .GitDir , func () {
578+ s .onHEADChanged (rt .ctx , rt )
562579 })
563580
564581 if rt .mfs != nil {
565582 go func () {
566- _ = rt .mfs .Join (ctx )
583+ _ = rt .mfs .Join (rt . ctx )
567584 }()
568585 }
569586}
@@ -654,7 +671,9 @@ func (s *Service) unmount(id model.RepoID) {
654671}
655672
656673func (s * Service ) stopRuntime (rt * repoRuntime ) {
657- close (rt .stop )
674+ if rt .cancel != nil {
675+ rt .cancel ()
676+ }
658677 if rt .mfs != nil {
659678 _ = rt .mfs .Unmount ()
660679 }
0 commit comments