Artifact a822a252af3e370342838612386f3f9692558b8c07f972403ab43e2bcd96d2dd:
- File
internal/verify/verify.go
— part of check-in
[33959a6e3e]
at
2026-10-12 17:34:38
on branch main
— fix(verify): derive Rebuild state through the crosslink sweep (#285) (#336)
verify.Rebuild kept its own partial derivation of repository state: it wrote event/plink/mlink/tagxref rows only for check-ins and control artifacts, applied tags from artifacts the crosslink would refuse, skipped absent tag targets and parents instead of reserving phantoms, wrote no plink baseid, and computed leaves as "no child at all" regardless of branch. Against fossil rebuild it missed hundreds of event, plink, tagxref and leaf rows on real repositories.
Make Rebuild empty the crosslink-derived tables and run the same sweep a clone runs, so derived state has a single owner. The three partial derivation files (rebuild_tags.go, rebuild_manifest.go, rebuild_leaves.go) are deleted; Rebuild keeps only what the sweep does not do -- blob hash verification and the unclustered/unsent bookkeeping.
A rebuild is terminal: nothing more will arrive, so a check-in whose file or baseline content is absent must be linked with phantoms rather than deferred for a blob that never comes, as fossil's add_one_mlink does via uuid_to_rid(...,1). Add manifest.CrosslinkRebuild, a sweep that does not defer; on a complete repository it is identical to Crosslink. Rebuild's leaf rule now follows fossil's branch-aware sweep, so an untagged parentless check-in is no longer a leaf -- the same Section 7.3 deviation the sweep and per-artifact paths already carry. (user: [email protected], size: 3222) [annotate] [blame] [check-ins using]
// Package verify provides comprehensive repository verification and rebuild. package verify import ( "time" "github.com/danmestas/go-libfossil/internal/content" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/repo" ) // verifyCacheBytes bounds the expanded content one Verify or Rebuild blob-check // keeps live. checkBlobs expands every blob once; Verify's checkCheckins then // re-expands the same blobs, so one cache shared across those sweeps walks each // blob's delta chain once instead of once per sweep. Rebuild's own derivation // runs through manifest.Crosslink, which carries its own expansion cache. A miss // costs throughput, not correctness; blob content is immutable and neither pass // rewrites it, so no cached entry can go stale. // // It was once the same number as manifest.crosslinkCacheBytes and no longer // is: that budget was measured down against clone wall time and peak RSS // (see its comment), and this pass has not been measured the same way. Sharing // a number is not a reason to inherit someone else's measurement. const verifyCacheBytes = 256 << 20 // IssueKind categorizes the type of verification issue found. type IssueKind int const ( IssueHashMismatch IssueKind = iota IssueBlobCorrupt IssueDeltaDangling IssuePhantomOrphan IssueEventMissing IssueEventMismatch IssueMlinkMissing IssuePlinkMissing IssueTagxrefMissing IssueFilenameMissing IssueLeafIncorrect IssueMissingReference ) // Issue represents a single verification problem found in the repository. type Issue struct { Kind IssueKind RID libfossil.FslID UUID string Table string Message string } // Report contains the results of a repository verification pass. type Report struct { Issues []Issue BlobsChecked int BlobsOK int BlobsFailed int BlobsSkipped int MissingRefs int TablesRebuilt []string Duration time.Duration } // OK returns true if no issues were found during verification. func (r *Report) OK() bool { return len(r.Issues) == 0 } // addIssue appends a new issue to the report. func (r *Report) addIssue(issue Issue) { r.Issues = append(r.Issues, issue) } // Verify performs comprehensive repository verification. // It checks blob integrity, delta chains, phantom records, and derived tables. // Returns a report of all issues found. Never stops early - reports all problems. func Verify(r *repo.Repo) (*Report, error) { if r == nil { panic("verify: nil repo") } start := time.Now() report := &Report{} // One expansion cache for the whole pass; see verifyCacheBytes. cache := content.NewCache(verifyCacheBytes) // Phase 1: Blob integrity (content hash verification) if err := checkBlobs(r, report, cache); err != nil { return nil, err } // Phase 2: Structural integrity (delta chains, phantoms) if err := checkDeltaChains(r, report); err != nil { return nil, err } if err := checkPhantoms(r, report); err != nil { return nil, err } // Phase 3: Derived tables (event, mlink, plink, tagxref, filename, leaf) if err := checkDerived(r, report, cache); err != nil { return nil, err } report.Duration = time.Since(start) return report, nil }