Artifact 410d61723ce3cd91ff3bf3ff94c1ea6efb3ee29f23fafff877c2ad855032012d:
- File
internal/checkout/status.go
— part of check-in
[3672f3d4fb]
at
2026-10-18 18:26:58
on branch feat/submanifest-reader
— feat(checkout): the sparse worktree of draft-fossil-sparse-cone-00 ยง4
A checkout created with a cone materializes, scans and tracks only the files it covers; vfile stays the full record, so fossil still reads the checkout. Commits carry uncovered files from the baseline by hash, update applies uncovered changes to the record only, and tracking, renaming, reverting or merging across the cone is refused naming it. SetCone widens and narrows. (user: [email protected], size: 3321) [annotate] [blame] [check-ins using]
package checkout import ( "errors" "fmt" "os" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/vfile" ) // HasChanges reports whether any file of the current version is added, // removed, renamed or modified. It reads only the checkout database: an edit // on disk counts once a scan has recorded it, but adds, removals and renames // are always seen. Missing files are not, because that needs the disk. // // Panics if c is nil (TigerStyle precondition). func (c *Checkout) HasChanges() (bool, error) { if c == nil { panic("checkout.HasChanges: nil *Checkout") } rid, _, err := c.Version() if err != nil { return false, fmt.Errorf("checkout.HasChanges: %w", err) } if rid < 0 { panic("checkout.HasChanges: negative checkout version") } rows, err := c.managedRows(rid) if err != nil { return false, fmt.Errorf("checkout.HasChanges: %w", err) } for _, r := range rows { if vfile.Classify(r, false) != ChangeNone { return true, nil } } return false, nil } // VisitChanges calls fn for each changed file of version vid, classified by // vfile.Classify. With scan=true it first runs ScanChanges(ScanHash) and // checks each file on disk, so edits and missing files are reported; with // scan=false it reports what the checkout database records. // // If fn returns a non-nil error, iteration stops and that error is returned. // // Panics if c is nil (TigerStyle precondition). func (c *Checkout) VisitChanges(vid libfossil.FslID, scan bool, fn ChangeVisitor) error { if c == nil { panic("checkout.VisitChanges: nil *Checkout") } if fn == nil { panic("checkout.VisitChanges: nil visitor") } if scan { if err := c.ScanChanges(ScanHash); err != nil { return fmt.Errorf("checkout.VisitChanges: %w", err) } } rows, err := c.managedRows(vid) if err != nil { return fmt.Errorf("checkout.VisitChanges: %w", err) } for _, r := range rows { missing := false if scan { if missing, err = c.isMissing(r); err != nil { return fmt.Errorf("checkout.VisitChanges: %w", err) } } change := vfile.Classify(r, missing) if change == ChangeNone { continue } entry := ChangeEntry{ Name: r.Pathname, Change: change, VFileID: libfossil.FslID(r.ID), IsExec: r.IsExe != 0, IsLink: r.IsLink != 0, OrigName: r.RenamedFrom(), } if err := fn(entry); err != nil { return fmt.Errorf("checkout.VisitChanges: visitor for %s: %w", r.Pathname, err) } } return nil } // isMissing reports whether a row's file is absent from disk, the way // fossil's changes command decides MISSING: anything that is not a regular // file counts. A removed row is never missing; it is meant to be gone. func (c *Checkout) isMissing(r vfile.Row) (bool, error) { if r.Pathname == "" { panic("checkout.isMissing: empty pathname") } if c.env == nil { panic("checkout.isMissing: nil env") } if r.Deleted > 0 { return false, nil } fullPath, err := c.safePath(r.Pathname) if err != nil { return false, fmt.Errorf("path traversal in %s: %w", r.Pathname, err) } info, err := c.env.Storage.Stat(fullPath) if err != nil { if errors.Is(err, os.ErrNotExist) { return true, nil } return false, fmt.Errorf("stat %s: %w", fullPath, err) } return !info.Mode().IsRegular(), nil }