Artifact 537901157b7a35af627b9bf550156f36a6f94cf355145621d9ed346f64a47491:
- File
internal/sync/handler_uv.go
— part of check-in
[f5744359d3]
at
2026-04-19 16:30:54
on branch trunk
— Initial open source release
libfossil is a pure-Go implementation of the Fossil SCM, reading and writing the same .fossil SQLite repository format as upstream Fossil.
This is the first public release of code that previously lived in a private repository. The module path is github.com/danmestas/libfossil and the binary is named libfossil to avoid $PATH collisions with upstream fossil.
Included: - Repo lifecycle (create, clone), checkout/checkin, timeline, merge, diff, annotate, shun, tag, branch, bisect, stash, undo/redo - Sync-over-HTTP protocol (client and handler) - Two SQLite drivers: modernc (pure Go, default) and ncruces (wasm-capable) - Observer interfaces (SyncObserver, CheckoutObserver) with an optional OpenTelemetry implementation in observer/otel (separate submodule) - Deterministic simulation testing harness with BUGGIFY fault injection - Kong-based embeddable CLI under cli/ and a standalone binary under cmd/libfossil/ (user: [email protected], size: 4604) [annotate] [blame] [check-ins using]
package sync import ( "fmt" "github.com/danmestas/libfossil/internal/auth" "github.com/danmestas/libfossil/internal/hash" "github.com/danmestas/libfossil/internal/uv" "github.com/danmestas/libfossil/internal/xfer" ) func (h *handler) handlePragmaUVHash(clientHash string) error { if h.uvCatalogSent { return nil } if err := uv.EnsureSchema(h.repo.DB()); err != nil { return fmt.Errorf("handler.handlePragmaUVHash: ensure schema: %w", err) } localHash, err := uv.ContentHash(h.repo.DB()) if err != nil { return fmt.Errorf("handler.handlePragmaUVHash: content hash: %w", err) } if localHash == clientHash { return nil // already in sync } h.uvCatalogSent = true h.resp = append(h.resp, &xfer.PragmaCard{Name: "uv-push-ok"}) entries, err := uv.List(h.repo.DB()) if err != nil { return fmt.Errorf("handler.handlePragmaUVHash: list: %w", err) } for _, e := range entries { hashVal := e.Hash if hashVal == "" { hashVal = "-" } h.resp = append(h.resp, &xfer.UVIGotCard{ Name: e.Name, MTime: e.MTime, Hash: hashVal, Size: e.Size, }) } return nil } func (h *handler) handleUVIGot(c *xfer.UVIGotCard) error { if c == nil { panic("handler.handleUVIGot: c must not be nil") } if err := uv.EnsureSchema(h.repo.DB()); err != nil { return fmt.Errorf("handler.handleUVIGot: ensure schema: %w", err) } _, localMtime, localHash, err := uv.Read(h.repo.DB(), c.Name) if err != nil { return fmt.Errorf("handler.handleUVIGot: read %q: %w", c.Name, err) } status := uv.Status(localMtime, localHash, c.MTime, c.Hash) switch { case status == 0 || status == 1: h.resp = append(h.resp, &xfer.UVGimmeCard{Name: c.Name}) case status == 2: if _, err := h.repo.DB().Exec("UPDATE unversioned SET mtime=? WHERE name=?", c.MTime, c.Name); err != nil { return fmt.Errorf("handler.handleUVIGot: update mtime %q: %w", c.Name, err) } if err := uv.InvalidateHash(h.repo.DB()); err != nil { return fmt.Errorf("handler.handleUVIGot: invalidate hash: %w", err) } case status == 4 || status == 5: if err := h.sendUVFile(c.Name); err != nil { return fmt.Errorf("handler.handleUVIGot: send %q: %w", c.Name, err) } } return nil } func (h *handler) handleUVGimme(c *xfer.UVGimmeCard) error { if c == nil { panic("handler.handleUVGimme: c must not be nil") } if h.buggify != nil && h.buggify.Check("handler.handleUVGimme.skip", 0.05) { return nil } return h.sendUVFile(c.Name) } func (h *handler) sendUVFile(name string) error { if name == "" { panic("handler.sendUVFile: name must not be empty") } content, mtime, fileHash, err := uv.Read(h.repo.DB(), name) if err != nil { return fmt.Errorf("handler.sendUVFile: read %q: %w", name, err) } if fileHash == "" { h.resp = append(h.resp, &xfer.UVFileCard{ Name: name, MTime: mtime, Hash: "-", Size: 0, Flags: xfer.UVFlagDeletion, }) return nil } h.resp = append(h.resp, &xfer.UVFileCard{ Name: name, MTime: mtime, Hash: fileHash, Size: len(content), Flags: 0, Content: content, }) return nil } func (h *handler) handleUVFile(c *xfer.UVFileCard) error { if c == nil { panic("handler.handleUVFile: c must not be nil") } if !auth.CanPushUV(h.caps) { h.resp = append(h.resp, &xfer.ErrorCard{ Message: fmt.Sprintf("uvfile %s denied: insufficient capabilities", c.Name), }) return nil } if !h.pushOK { h.resp = append(h.resp, &xfer.ErrorCard{ Message: fmt.Sprintf("uvfile %s rejected: no push card", c.Name), }) return nil } if h.buggify != nil && h.buggify.Check("handler.handleUVFile.drop", 0.03) { return nil } if err := uv.EnsureSchema(h.repo.DB()); err != nil { return fmt.Errorf("handler.handleUVFile: ensure schema: %w", err) } // Validate hash if content present. if c.Flags&xfer.UVFlagNoPayload == 0 { if c.Content == nil { panic("handler.handleUVFile: flags indicate payload but Content is nil") } computed := hash.ContentHash(c.Content, c.Hash) if computed != c.Hash { h.resp = append(h.resp, &xfer.ErrorCard{ Message: fmt.Sprintf("uvfile %s: hash mismatch", c.Name), }) return nil } } // Double-check status. _, localMtime, localHash, err := uv.Read(h.repo.DB(), c.Name) if err != nil { return fmt.Errorf("handler.handleUVFile: read %q: %w", c.Name, err) } status := uv.Status(localMtime, localHash, c.MTime, c.Hash) if status >= 2 { return nil } // Apply. if c.Hash == "-" { return uv.Delete(h.repo.DB(), c.Name, c.MTime) } if c.Content != nil { return uv.Write(h.repo.DB(), c.Name, c.Content, c.MTime) } return nil }