Artifact afdd0f9faa27f2bd0ccb9937f43b86d35570d8bfaf35b73b6ff40798ed32e73d:
- File
horizon_cost.go
— part of check-in
[834b28d9fe]
at
2026-10-23 03:02:17
on branch feat/horizon-cones
— feat(cli): report what each horizon rule would hold
Draft §3.4 asks a tool that suggests a horizon to show, before the choice, what each rule would hold in check-ins and bytes. repo cone cost <check-in> prints that for descendants and leaves from the local repository, and Repo.HorizonCost returns the figures. The --horizon help of repo clone and repo cone store points to it, noting that a clone has no repository yet. (user: [email protected], size: 2618) [annotate] [blame] [check-ins using] [more...]
package libfossil import ( "context" "database/sql" "errors" "fmt" "github.com/danmestas/go-libfossil/internal/cone" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/horizon" "github.com/danmestas/go-libfossil/internal/manifest" ) // HorizonRuleCost is what one rule would hold for a horizon at a check-in // (draft-fossil-horizon-00 §3.4): the check-ins of the held set K and the // blobs those check-ins' F cards name, the contents a clone at that horizon // keeps. type HorizonRuleCost struct { Rule HorizonRule Checkins int // check-ins in K Blobs int // distinct blobs K's check-ins name, held here StoredBytes int64 // what those blobs occupy in the repository, compressed and delta-encoded ExpandedBytes int64 // their sizes once expanded } // HorizonCost reports, for each rule in order (descendants, leaves), what a // horizon at checkin would hold, so the choice of rule is made with its cost // in view (§3.4). checkin is any name ResolveVersion accepts. The figures are // read from this repository, which must hold the history: a blob it does not // hold is left out of the byte counts, and a sub-manifest it lacks is an // error. func (r *Repo) HorizonCost(checkin string) ([]HorizonRuleCost, error) { rid, err := r.ResolveVersion(checkin) if err != nil { return nil, err } ctx := context.Background() rules := []HorizonRule{HorizonDescendants, HorizonLeaves} out := make([]HorizonRuleCost, 0, len(rules)) for _, rule := range rules { k, err := horizon.HeldSet(ctx, r.inner.DB(), libfossil.FslID(rid), cone.HorizonRule(rule)) if err != nil { return nil, fmt.Errorf("libfossil: horizon cost: %w", err) } cost := HorizonRuleCost{Rule: rule, Checkins: len(k)} seen := map[string]bool{} for _, c := range k { files, err := manifest.ListFiles(r.inner, c) if err != nil { return nil, fmt.Errorf("libfossil: horizon cost: %w", err) } for _, f := range files { if seen[f.UUID] { continue } seen[f.UUID] = true var size, stored int64 err := r.inner.DB().QueryRow( "SELECT size, coalesce(length(content), 0) FROM blob WHERE uuid=? AND size>=0", f.UUID, ).Scan(&size, &stored) if errors.Is(err, sql.ErrNoRows) { continue // a phantom or an elided blob: not held, so not counted } if err != nil { return nil, fmt.Errorf("libfossil: horizon cost: blob %s: %w", f.UUID, err) } cost.Blobs++ cost.ExpandedBytes += size cost.StoredBytes += stored } } out = append(out, cost) } return out, nil }