Artifact 8a01a6bb04077c6fb1d3907c4e9fc6ad8d0e645b76533b0170cc44dc3851da75:
- File
internal/hash/policy.go
— part of check-in
[1c02c944ec]
at
2026-08-07 17:33:21
on branch trunk
— fix(db): seed hash-policy so new repos name artifacts like fossil init (#225) (#226)
SeedConfig never wrote hash-policy, so naming fell back to deriving from the repo's own artifacts — and an empty repo has none, settling on SHA1 permanently. Seed sha3, which is what fossil init writes.
Clone drops the row the same way it drops project-code: a clone adopts its source's policy, and an absent row derives naming from the artifacts that arrive.
Moves the policy enum to db so the seed and hash.NamingFor read one definition, and updates fixtures that hardcoded SHA1 names to ask the repo how it names artifacts. (user: [email protected], size: 2697) [annotate] [blame] [check-ins using]
package hash import ( "strconv" "github.com/danmestas/go-libfossil/db" ) // Alg is one of the two algorithms Fossil names artifacts with. type Alg int const ( AlgSHA1 Alg = iota AlgSHA3 ) // Hash returns a's name for content. nil content is the empty artifact. func (a Alg) Hash(content []byte) string { if content == nil { content = []byte{} } if a == AlgSHA3 { return SHA3(content) } return SHA1(content) } // Other returns the algorithm a is not. func (a Alg) Other() Alg { if a == AlgSHA3 { return AlgSHA1 } return AlgSHA3 } func (a Alg) String() string { if a == AlgSHA3 { return "sha3" } return "sha1" } // Naming is how one repo names the artifacts it is about to create. type Naming struct { // New is the algorithm for a new artifact's name. New Alg // ReuseLegacy reports whether content already stored under the other // algorithm's name may keep that name instead of being stored again. // False under sha3-only and shun-sha1, which reject legacy names. ReuseLegacy bool } // NamingFor reads the hash-policy of the repo behind q. // // A repo with no hash-policy row — one predating the setting, or created by // a tool that never wrote it — is read as "auto", so its own artifacts // decide: SHA3 if it already holds a SHA3-named artifact, SHA1 otherwise. // An empty repo with no policy therefore stays on SHA1. // // Read failures fall back the same way rather than being reported: a missing // row is the ordinary case and cannot be told apart from a broken read here, // and a genuinely broken database fails on the caller's very next query. func NamingFor(q db.Querier) Naming { if q == nil { panic("hash.NamingFor: q must not be nil") } policy := db.HashPolicyAuto var raw string if err := q.QueryRow("SELECT value FROM config WHERE name='hash-policy'").Scan(&raw); err == nil { if n, err := strconv.Atoi(raw); err == nil { policy = n } } n := Naming{New: AlgSHA1, ReuseLegacy: policy < db.HashPolicySHA3Only} switch { case policy >= db.HashPolicySHA3: n.New = AlgSHA3 case policy == db.HashPolicyAuto && hasSHA3Artifact(q): // Fossil promotes "auto" to "sha3" as soon as any SHA3 artifact // enters the repo; deriving that from the artifacts themselves // gives the same answer without rewriting the config row. n.New = AlgSHA3 } return n } // hasSHA3Artifact reports whether the repo already holds an artifact named // with SHA3. A false answer on a read error is the safe one: it keeps naming // on SHA1, which every Fossil version accepts. func hasSHA3Artifact(q db.Querier) bool { var one int err := q.QueryRow("SELECT 1 FROM blob WHERE length(uuid)=64 LIMIT 1").Scan(&one) return err == nil }