48 descendants and 50 ancestors of e9eb2cabac679841
|
2026-10-31
| ||
| 16:48 |
Merge pull request #531 from danmestas/release/v0.17.0
chore(release): v0.17.0 leaf check-in: c530a8e967 user: [email protected] tags: main, trunk, v0.17.0 | |
| 16:41 | chore(release): v0.17.0 check-in: d9342fa02b user: [email protected] tags: main, trunk | |
| 16:41 |
Merge pull request #530 from danmestas/docs/wasm-size-tags
docs: the libfossil_noslog tag, and how the publisher engine gets smaller still check-in: 33ecaf10e6 user: [email protected] tags: main, trunk | |
| 16:33 | docs: regenerate llms-full.txt for the install guide changes check-in: f103757215 user: [email protected] tags: main, trunk | |
| 16:23 |
docs: the libfossil_noslog tag, and how the publisher engine gets smaller still
The TinyGo section of the install guide and examples/tinygo described libfossil_nohttp but not libfossil_noslog, which only the changelog mentioned. Both now say what the tag leaves out and what changes. The install guide gains a "Smaller still" section pointing at the html-artifact-publisher engine's build: no scheduler, a reactor export, explicit panic unwinding and a patched standard library, with the behavior the patch changes. It says plainly that these steps are the publisher's and that this repository does not build that way. CI never compiled the code behind either tag, so a change could break a tagged embedder unnoticed until the publisher's CI ran against a release. A new build-size-tags job builds the library under both tags, vets the repository under libfossil_noslog, and runs the check-in linker's tests under it. check-in: b451efa274 user: [email protected] tags: main, trunk | |
| 14:22 |
Merge pull request #529 from danmestas/spike/wasm-size
Keep regexp, strings.Replacer and log/slog out of small builds check-in: f338cac5ba user: [email protected] tags: main, trunk | |
| 14:13 |
test(manifest): skip slog-capturing tests under libfossil_noslog; changelog
With the tag the diagnostics go to stderr, so the five tests that read them back from a slog handler skip. CHANGELOG notes the tag and that regexp and strings.Replacer no longer come in through the library. leaf check-in: 73812b88e0 user: [email protected] tags: main, spike/wasm-size, trunk | |
| 13:38 |
perf(wasm): keep regexp, strings.Replacer and log/slog out of small builds
Spike for a smaller TinyGo engine (html-artifact-publisher). Behaviour on every platform is unchanged: - repo: table names are checked with a byte loop, not a regexp, so a build that uses no other regexp leaves package regexp out (about 69 KB gzipped in the publisher's engine, with the engine's own two regexps). - fsltype: HTMLize escapes with a byte loop instead of a strings.Replacer (about 15 KB gzipped). - manifest: the package's diagnostics go through logDebug, logInfo and logWarn. By default they call log/slog as before. With the libfossil_noslog build tag they are written to stderr as plain lines and log/slog stays out (about 33 KB gzipped). Tests that assert on slog output need the default build. check-in: 2abfe81c2a user: [email protected] tags: main, spike/wasm-size, trunk | |
|
2026-10-29
| ||
| 16:28 |
Merge pull request #528 from danmestas/perf/memo-review-nits
perf(content): memo review follow-ups: per-hit expandability, purged-baseline test, lineage order check-in: 23c3e0486e user: [email protected] tags: main, trunk | |
| 16:19 |
docs(content): say treeExpandable checks the delta chain only
Corrupt or undecompressable content still passes the check, though a fresh read would skip that tree. check-in: 959c7e35ae user: [email protected] tags: main, perf/memo-review-nits, trunk | |
| 15:10 |
perf(content): check a kept tree's expandability per hit, cached per lineage
Each view that served a kept tree listed every rid its store could not expand, by a recursive walk seeded with all of the store's phantoms. A horizon-serving peer can hold tens of thousands of phantoms: on a copy of fossil-scm.org's store with 39.7k of them, that list cost about 100ms of every warm view (200ms against 105ms). A hit now walks the kept tree's own chain of delta sources up to the first rid whose answer is known. Within a lineage an answer never changes, so views publish the answers they found and the memo keeps those of its latest lineage, which replaces them; each delta step is read once per lineage, for the trees actually looked up. The per-lineage scan for delta sources a store holds no row for goes with it: the walk meets a missing row itself. On fossil-scm.org the first view of a lineage to serve 1000 kept trees pays about 10ms; whole history after a full read is unchanged. The stock-fossil shun test passed with both guards removed: shunning a check-in drops it from the held set, so its tree is never asked for. It is replaced by a purged baseline that the held set's delta manifests go on citing, with the horizon asserted placed. Stock fossil never leaves a store so (its purge refuses a baseline without its deltas, and rebuild leaves those uncrosslinked), so the test deletes the baseline's rows as such a purge would, from a store fossil wrote; a second case puts another blob at the freed rid, which only the held-name check catches. Also documents why a lineage numbered at view start rather than at the read snapshot is safe: a view on an older snapshot may publish over a newer store, which costs the next view an extension or a full read and is never wrong. leaf check-in: 39b24649b0 user: [email protected] tags: main, perf/memo-review-nits, trunk | |
| 14:58 |
Merge pull request #527 from danmestas/perf/463-memo-trees
perf(content): keep the memo's trees for the life of the memo (HZ-6.2-10) check-in: dd58b95c70 user: [email protected] tags: main, trunk | |
| 14:01 |
fix(content): serve a kept tree only where a fresh read would give it
Review of the tree memo found it could classify differently warm than cold. A kept tree was served whenever the store held its name at its rid, even when the store could no longer expand it (a delta whose source became a phantom or lost its row), where a fresh read skips the tree. Each view now lists, once, the rids its store cannot expand: a recursive walk of the delta table seeded with the phantoms and the delta sources the store holds no row for. Those sources only appear when a row is deleted, which forces a full read, so they are found once per lineage (about 20ms on fossil-scm.org) and the per-view walk costs about 0.3ms. The phantom check folds into it. Publish dropped the trees a view read whenever the memo had gone past that view's store, though trees do not depend on the store; they are now kept either way. A view over a store read in full after the memo's is no longer taken for a stale one, so a store whose highest rid was deleted stops being read in full on every view. The names bound that replaced the byte cap counted pieces, not names, so one publish could pass it; it now counts the names each piece adds, on the fresh-index path too, and is passed as a parameter so it is tested. The tests compare the names a horizon's walk produces, memo against fresh, which classification alone can hide: a tree's lost nodes also stop its files counting as files. They lose trees as phantoms, as deleted nodes, behind phantom and deleted delta sources, and by a shun and rebuild in stock fossil; removing either check fails them. check-in: c50ff7c9e2 user: [email protected] tags: main, perf/463-memo-trees, trunk | |
| 12:46 |
perf(content): keep the memo's trees for the life of the memo
HZ-6.2-10 asks that an origin read each structural artifact's F cards once per store. The memo's tree index already kept fossil-scm.org's whole tree history in about 11MiB, yet it still read trees again in two cases: past a 64MiB bound, and after any full read of the store, which dropped the index with its lineage. Neither was needed. A tree piece is what its name's bytes say, so it is valid in every store that holds the name; the rid it was read at is checked against the view's own rid-to-name map, and a renumbered store reads it once more and moves it to its rid. The index grows with the store like the reference graph beside it, so the byte bound goes; only a names bound at the most artifacts a classified store can hold remains, which one store cannot reach. On the fossil-scm.org clone a horizon 2000 back after a full read now takes 1.0-1.8s instead of 4.3-6.2s, and the whole history after a full read reads no tree (1.3-1.5s, from 14-17s). Cold runs and peak heap are unchanged. HZ-6.2-10 is now covered; the trees still enter the graph when a walk first reaches them, as the reverted eager read cost 4-5x cold. leaf check-in: bfce209a24 user: [email protected] tags: main, perf/463-memo-trees, trunk | |
| 12:46 |
test(content): measure the memo's trees after a full read and over all history
The corpus test timed horizons cold and warm, but not the case HZ-6.2-10 left open: a full read of the store (an arrival a held artifact cited) dropped every tree the memo had read. It now times a horizon again after one, and a second test walks the whole history to report what keeping every tree costs in heap and time. check-in: e690ff7ad2 user: [email protected] tags: main, perf/463-memo-trees, trunk | |
| 05:35 |
Merge pull request #525 from danmestas/feat/horizon-peer-serving
fix(sync): a horizon store refuses a pull it does not hold and mints no seed check-in: 301c8e2337 user: [email protected] tags: main, trunk | |
| 04:57 |
refactor(sync): keep an allowlist of reply cards on a horizon refusal
A refusal keeps only gimmes, errors, messages and the session-level pragmas, so a pull-side card type added later is dropped until judged safe. The refused-pull case now asks for every pull-side card kind and asserts the reply holds only allowlisted types. check-in: fb36eb5653 user: [email protected] tags: feat/horizon-peer-serving, main, trunk | |
| 04:21 | Merge remote-tracking branch 'origin/main' into feat/horizon-peer-serving leaf check-in: fa65d2c2d5 user: [email protected] tags: feat/horizon-peer-serving, main, trunk | |
| 03:45 |
fix(sync): a refused horizon sync still takes the peer's push
Re-review of #525. Resetting the reply after the card loop dropped the gimmes that answer the client's igot announcements. The real client sends only igots in its first round, so its push never landed. - Server: a §6.4 refusal drops only what the pull half put in the reply. That is content, announcements and the pragmas serving the horizon. The push half's cards stay, its gimmes and any push error, and the horizon-not-held error goes last, since a client stops reading at an error card. - Client: on a horizon-not-held refusal the pull stops and later requests carry no pull card. Rounds go on answering gimmes until the push converges, bounded by MaxRounds, and Sync then returns the refusal. A structural store does not read the refusal as a reason to fall back. - The refused-push case now drives the real client: an earlier-C peer with a check-in of its own syncs, and the store holds that check-in while the peer is sent no content. A push error survives the refusal. - The requirement table and concept doc say why taking the push is consistent with §6.4's "no content". check-in: a557971c28 user: [email protected] tags: feat/horizon-peer-serving, main, trunk | |
| 03:30 |
Merge pull request #526 from danmestas/perf/wasm-size-and-refusal
Return receive-budget refusals as *RefusedError; libfossil_nohttp tag cuts the TinyGo engine ~97 KB check-in: 0ffed7ff5f user: [email protected] tags: main, trunk | |
| 03:21 |
docs: libfossil_nohttp is for library embedders, who supply their own transport
The cli package and examples/tinygo, which calls NewHTTPTransport, do not build with the tag; a tagged embedder supplies its own Transport and serves through HandleSyncWithOpts. check-in: 4069607141 user: [email protected] tags: main, trunk | |
| 03:06 |
perf(wasm): add a libfossil_nohttp build tag that leaves out net/http
Under TinyGo, importing net/http at all links its TLS and X.509 code (crypto/x509, encoding/asn1, math/big's integers, AES-GCM, SHA-3, netip) whether or not a request is ever made: a five-line program that calls only http.StatusText grows by about 430 KB raw. The library imports it for its HTTP client transport and its HTTP server, which a program that serves sync through HandleSyncWithOpts and supplies its own transport never calls. Built with -tags libfossil_nohttp, the library leaves out NewHTTPTransport, WithHTTPClient, Repo.Pull, Repo.XferHandler and Repo.ServeHTTP, and the internal/sync client, server, redirect handling and peer-address trace they use. HandleSync, HandleSyncWithOpts, Sync over a host Transport and everything else are unchanged. Without the tag nothing changes. The retry classifier keeps the two statuses it read from net/http as constants, and DNS fault classification moves to its own file so it stays in the tagged build. For the html-artifact-publisher engine (TinyGo 0.43.0, Go 1.26.0) the tag takes the gzipped binary from 1,100,251 to 1,003,279 bytes. A test builds the library, db and simio with the tag for the host and js/wasm and checks none links net/http, net/http/httptrace or crypto/tls, and that the untagged library still does. check-in: 9594cf3bae user: [email protected] tags: main, trunk | |
| 03:06 |
perf(fsltype): format a 53-bit text Julian day in float64, no math/big
Fossil's %.17g of a text Julian day is computed in the platform's long double, which this package emulates with big.Float at that precision. A 53-bit long double is a double, and big.Float at 53 bits with round-to-nearest-even is exactly IEEE double arithmetic over this range, so that case (wasm, macOS arm64, Windows) is now computed in float64, each product converted explicitly so none fuses into a multiply-add. The big.Float path remains for the 64- and 113-bit long doubles and is left out of wasm builds, whose long double is 53 bits; a wasm TinyGo binary loses math/big's floating point, about 5 KB gzipped. A test checks the float64 path against the big.Float one at 53 bits over 50,000 random times and every ~1 s of three days, and the existing fossil-binary sweep passes unchanged. Stored times do not change. check-in: f7eb80beae user: [email protected] tags: main, trunk | |
| 03:06 |
feat(sync): return a receive-budget refusal to the host as *RefusedError
With HandleOpts.MaxStoredBytes set, a refused push rolls its request transaction back and replies with one error card. A host whose storage treats any rollback as an abort of the whole call (a Durable Object running the request in one transactionSync) discards that reply, so the client saw only "transaction rolled back". HandleSyncWithOpts now returns the encoded reply together with a *RefusedError whose Message is the error card's text, matchable with errors.As. Such a host can report or render the refusal itself without parsing xfer; a host serving HTTP sends the reply as before. Only the budget refusal reports this way, so no released behaviour changes: the budget is unreleased and opt-in. check-in: dd7401ad01 user: [email protected] tags: main, trunk | |
| 03:02 |
fix(sync): judge the horizon refusal after the push, serve later peers
Review of #525. - A requested C this store cannot place is a peer ahead of it, not one asking for history it lacks, and is served what the store holds. A peer at a later C the store holds is placed inside its held set and served. - The refusal is judged after the card loop, once the pull card has passed its permission check and the push half is stored. Only the pull half is refused: the reply is reset to the one error card, the request is not rolled back, and a push in the same sync is kept. - The held sets are computed through the classification memo, which keeps them per store and drops them when the store changes; the inventory reuses the view the judgment read. - The refusal begins with the machine-readable prefix horizon-not-held:, which the client matches instead of the whole text. - The fallback remedy moves from a new result field into the existing diagnostics, prefixed horizon-fallback:: SyncResult.Errors and CloneResult.Messages. The CLI still prints it. check-in: ad975ea85b user: [email protected] tags: feat/horizon-peer-serving, main, trunk | |
| 02:12 | refactor(sync): separate the seeded-store check's error from its answer check-in: 2cac21987f user: [email protected] tags: feat/horizon-peer-serving, main, trunk | |
| 02:06 |
fix(sync): a horizon store refuses a pull it does not hold and mints no seed
A server over a horizon clone answered a horizon pull or sync at an earlier C from its own partial graph. It now refuses any horizon pull whose held set is not a subset of its own with one error card and no content, rolled back whole, and a go-libfossil client fails the round on that reply (draft-fossil-horizon-00 §6.4). partial-mirror serves it, as it does an uncovered clone. The clone coverage check now judges the horizon the request asked for, not the depth serving cut it to. A store at a structural horizon no longer mints a seed from its own seed: it answers a structural request at the content level (§6.2.3) and names its horizon-history-url. The requester falls back and reports HorizonRemedy, naming C and the dense origin that holds the manifests below it; the CLI prints it. The concept doc states when a horizon store may serve a horizon peer. Conformance covers §6.4 on the pull path, a structural store answering as content, and the fallback a structural clone takes from such a peer; zit pulls from a go-libfossil horizon store in both outcomes. Closes #490 check-in: 8802c5704f user: [email protected] tags: feat/horizon-peer-serving, main, trunk | |
| 01:51 |
Merge pull request #524 from danmestas/feat/receive-size-budget
feat(sync): per-store artifact size budget on the receive path check-in: d385bc8746 user: [email protected] tags: main, trunk | |
| 01:42 |
fix(sync): name a too-big file only to a reader, never by a private check-in
The budget refusal names the file and check-in only when the requester may read the repository, and phantomName skips private check-ins. HandleOpts, XferHandler and ServeHTTP document that the default handler applies no budget, so a host that needs one serves HandleSyncWithOpts itself. Tests: an artifact held in full is not refused; a bare phantom is; a path holding card syntax goes out as one encoded error line; a sync round with pull and igot cards is refused whole with FilesReceived 0. Atomicity checks now cover unclustered, phantom, delta and mlink alongside blob. check-in: 41102e04af user: [email protected] tags: feat/receive-size-budget, main, trunk | |
| 01:35 | Merge remote-tracking branch 'origin/main' into feat/receive-size-budget check-in: 8426695b6c user: [email protected] tags: feat/receive-size-budget, main, trunk | |
| 00:48 |
Merge pull request #522 from danmestas/perf/memo-tree-encoding
perf(content): keep the memo's trees as interned id sets and differences check-in: 6d860dc574 user: [email protected] tags: main, trunk | |
| 00:42 |
Merge remote-tracking branch 'origin/main' into feat/receive-size-budget
# Conflicts: # CHANGELOG.md check-in: 767f64016a user: [email protected] tags: feat/receive-size-budget, main, trunk | |
| 00:39 |
content: compare the intern count as uint64 so 32-bit targets build
math.MaxUint32 overflows int on TinyGo's wasm target. check-in: 53338a5536 user: [email protected] tags: main, perf/memo-tree-encoding, trunk | |
| 00:24 | Merge remote-tracking branch 'origin/main' into perf/memo-tree-encoding check-in: e5b5f30ace user: [email protected] tags: main, perf/memo-tree-encoding, trunk | |
| 00:24 |
Merge pull request #523 from danmestas/feat/attachments
feat(attachments): fossil-compatible attachments API, with Quarry external stubs check-in: 6520f5dbb0 user: [email protected] tags: main, trunk | |
| 00:15 |
fix(attachments): stamp each attachment write after the latest for its name
A default-time deletion could share the addition's millisecond, leaving both rows latest: the deleted file stayed listed but could not be read. A synced attachment stamped in the future made a default-time deletion a silent no-op. Every write is now stamped strictly after the latest for its name -- now, or a millisecond after it -- and an explicit earlier or equal time is refused, for additions as for deletions. The ambiguity error names the kinds that matched, and an external name of "." or ".." is refused up front. leaf check-in: cfc0ab51e8 user: [email protected] tags: feat/attachments, main, trunk | |
| 00:10 |
feat(attachments): add, delete, list and read attachments as fossil does
AttachmentAdd, AttachmentDelete, Attachments and AttachmentRead write and read the artifacts fossil 2.28's attach.c writes: the file as one ordinary blob and an A C D U Z control artifact naming it, a deletion as an A card with no source. Targets are a wiki page's exact name or a ticket's or tech note's id or unique prefix; a name two kinds answer to is refused. AttachmentAddExternal stores Quarry RFC 0016's stub in place of the bytes, with the mimetype on the N card; list and read report the reference and never hand the stub's text back as the file. check-in: f747958447 user: [email protected] tags: feat/attachments, main, trunk | |
| 00:01 |
feat(sync): per-store artifact size budget on the receive path
HandleOpts.MaxStoredBytes (internal and public) caps how many bytes one pushed artifact may occupy in blob.content as stored: a cfile card's stored form, otherwise the blob.Compress form of the file card's payload (the delta for a delta card). A plain payload whose compression bound fits is not compressed just to measure it. An artifact over the budget fails the request (h.fail): one error card naming the hash, stored size and limit, plus the file name and check-in when a crosslinked check-in already lists the phantom. The request transaction rolls back, so nothing from the round is stored and FilesReceived is 0. Stock fossil 2.28 prints the error on push. 0 keeps today's behaviour; clone and pull serving are not limited. Related to #459. leaf check-in: bd2efd6d5a user: [email protected] tags: feat/receive-size-budget, main, trunk | |
|
2026-10-28
| ||
| 23:43 |
perf(content): bound and test the memo's compact trees
Count the decoded file set a tree index keeps as the next difference's base toward memoTreeBytes, check a decoded id's gap without wrapping in uint64, and say what cloning the index on publish costs (about 0.5ms for fossil-scm.org's whole history, BenchmarkTreeIndexClone). TestMemoWideTreesMatchFresh classifies every cone over wide trees that rename, delete and add files, in all three manifest forms, with chains of differences 15 long and with a limit that starts the index again, before and after the store gains check-ins; a branch heads the chains trunk trees continue, so a trunk horizon decodes trees whose chain starts outside its held set. check-in: 7ba1eb6915 user: [email protected] tags: main, perf/memo-tree-encoding, trunk | |
| 23:40 |
Merge remote-tracking branch 'origin/main' into perf/memo-tree-encoding
# Conflicts: # CHANGELOG.md check-in: a90035689e user: [email protected] tags: main, perf/memo-tree-encoding, trunk | |
| 23:01 |
Merge pull request #520 from danmestas/feat/demand-fetch-reads
feat(horizon): targeted demand fetch for reads below the horizon check-in: c63cab2f36 user: [email protected] tags: main, trunk | |
| 22:58 | Keep blob.Load's query text, which read-count tests match on leaf check-in: 9e4f80b2df user: [email protected] tags: feat/chunked-blobs, main, trunk | |
| 22:52 |
Record chunked length in blob.content and verify parts on read
Review of the chunked-blob change found a missing last part read back short with no error, and a chunked write outside a transaction could leave a torn row that looked held. A chunked row now keeps its stored length in blob.content as an INTEGER. Reads check the parts run unbroken from seq 0, are full-sized but for the last, and add up to that length; anything else is ErrPartsCorrupt, and a NULL content is no longer mistaken for a chunked one. StoredLenSQL reads the length off the row and needs no schema probe. Chunked writes, and the phantom fill around them, run in a savepoint of the caller's transaction or a transaction of their own, so a failed part leaves nothing behind. A value needing more than 4096 parts is an error, not a panic, since stored values can come from peers. The cone and sparse local payload reads use ReadStoredExact, which loads a value only at the length already budgeted for. MaxValueBytes is documented as process-wide, and unversioned content as not chunked. check-in: 0fc4d176c3 user: [email protected] tags: feat/chunked-blobs, main, trunk | |
| 22:49 | Merge remote-tracking branch 'origin/main' into feat/demand-fetch-reads check-in: 2eb8cc938d user: [email protected] tags: feat/demand-fetch-reads, main, trunk | |
| 22:47 | Merge remote-tracking branch 'origin/main' into feat/chunked-blobs check-in: 0f72c73fe6 user: [email protected] tags: feat/chunked-blobs, main, trunk | |
| 22:44 | Merge remote-tracking branch 'origin/main' into perf/memo-tree-encoding leaf check-in: 6ae0bf4b3d user: [email protected] tags: main, perf/memo-tree-encoding, trunk | |
| 21:50 |
Merge pull request #519 from danmestas/conf/hz-4-1-1-zit-scope
conformance: HZ-4.1-1 scoped to zit and go-libfossil origins check-in: 1455a3803e user: [email protected] tags: main, trunk | |
| 21:24 | docs: regenerate conformance data for HZ-4.1-1 check-in: 71eb242f08 user: [email protected] tags: conf/hz-4-1-1-zit-scope, main, trunk | |
| 21:14 |
conformance: HZ-4.1-1 scopes horizon origins to zit and go-libfossil
Drop the stock-fossil caveat from the row, so it reports COVERED, and probe zit's reply to req-clusters under a horizon pragma (after a dense control). leaf check-in: e9eb2cabac user: [email protected] tags: conf/hz-4-1-1-zit-scope, main, trunk | |
| 14:48 |
Merge pull request #516 from danmestas/perf/cone-clone-resend
perf: carry a cone clone's forwarded delta chains across rounds check-in: b85673a6cb user: [email protected] tags: main, trunk | |
| 14:33 | Merge remote-tracking branch 'origin/main' into perf/cone-clone-resend check-in: 8f0f4fb0f5 user: [email protected] tags: main, perf/cone-clone-resend, trunk | |
| 14:33 |
Merge pull request #517 from danmestas/perf/cone-clone-rounds
fix(content): classify a fossil-written store's file versions from its tables check-in: d3574f0c74 user: [email protected] tags: main, trunk | |
| 14:24 | Merge remote-tracking branch 'origin/main' into perf/cone-clone-resend check-in: 5ca59e4ee3 user: [email protected] tags: main, perf/cone-clone-resend, trunk | |
| 14:24 | Merge remote-tracking branch 'origin/main' into perf/cone-clone-rounds check-in: a40dd502cd user: [email protected] tags: main, perf/cone-clone-rounds, trunk | |
| 14:24 |
Merge pull request #518 from danmestas/test/ticket-history-order
test: expect fossil's newest-first ticket history check-in: 3ef7f9fb1f user: [email protected] tags: main, trunk | |
| 14:22 |
docs(content): say the file-version decision applies to every store
The mlink-named, unclaimed versions are taken as file content in a store with the non-artifact record too, including this library's own between a Checkin that commits an artifact-shaped file and the sweep that links it. The comment now says so, and that the window closes as the sweep's batches add the claims the memo follows. check-in: dd3f875e99 user: [email protected] tags: main, perf/cone-clone-rounds, trunk | |
| 14:19 |
test(sync): make the re-deltify clone test depend on the run checksum
The re-deltified version now sits inside a carried run, and the unreached blob it is stored against is itself a delta of the run's tip, so the chain from the run's head keeps the run's length and differs only in its rids. Disabling the checksum in cloneRun now ends the clone with a phantom. check-in: caea59079f user: [email protected] tags: main, perf/cone-clone-resend, trunk | |
| 14:16 |
test: expect fossil's newest-first ticket history
fossil ticket history orders changes by time, newest first (tkt.c, ORDER BY 1 DESC), and leaves a same-second tie unordered. The test expected the original ticket before its amendment, which held only when both landed in the same second and the tie broke that way; CI failed it whenever the clock ticked between them. Stamp the amendment a minute later and expect it first. leaf check-in: 88082202ad user: [email protected] tags: test/ticket-history-order, trunk | |
| 14:07 |
Merge pull request #515 from danmestas/fix/seed-delta-drop
seedcheck: drop a seed completed as a delta in a later round check-in: 93cf3b65f6 user: [email protected] tags: main, trunk | |
| 13:59 |
seedcheck: count, privacy and savepoint follow-ups
A delta on a refused seed now counts as a new phantom only when it makes one, so a peer resending the same delta no longer holds a pull open to the round cap. The phantom it leaves is private when the card was, so its name is never asked for in a public sync. seedcheck's drop reuses db's savepoint instead of its own copy: db.Tx.WithSavepoint exposes what WithTx already does on a bound DB. check-in: f8446cdfcc user: [email protected] tags: fix/seed-delta-drop, main, trunk | |
| 13:58 |
fix(content): keep a partial non-artifact record from reading old files
A push into a store fossil wrote starts the non-artifact record with what arrived, and loadFiles then took only the record's versions as file content: every older version went back to be read raw, past the budget on a real repository. File versions are now the record's together with those mlink names and no claim table names (fileVersionsSQL), in a full load and in a memo's extension alike, and the memo's prefix counts the claims in every store, since file versions follow them whether or not a record exists. The horizon memo conformance case that pushes a manifest ahead of its blob into such a store now extends by the arrivals again. leaf check-in: 9cc2297ef8 user: [email protected] tags: main, perf/cone-clone-rounds, trunk | |
| 13:40 |
fix(sync): bound a clone cookie's parse and its runs' expansion
A request carries a cookie of any length, and splitting one on ':' or '.' before any bound allocated a string header per separator: 8 MB of dots cost hundreds of megabytes. cloneCapFields now refuses a cookie past maxCloneCookieBytes (64 KiB, well past the longest the server writes) before it splits, and splits with SplitN, as parseCloneRuns does. A forged cookie could also name one deep chain maxCloneRuns times. The server now rebuilds each head once and at most maxCloneRunRIDs rids in all, newest runs first, and the cookie keeps the newest maxCloneRuns runs rather than the oldest, since an old run may never be reached. A SHA3 artifact refused to an older peer was recorded as sent, and so carried in a run across rounds; it is now left out as a withheld tree is. TestConeCloneSurvivesRedeltify re-deltifies a forwarded version against an unreached in-cone blob mid-clone and checks the clone ends whole; it fails with a phantom if the run's checksum is not checked. leaf check-in: 624df80df2 user: [email protected] tags: main, perf/cone-clone-resend, trunk | |
| 13:35 |
fix(content): follow a record-less store's claim tables in the memo
Without the non-artifact record, file versions are those mlink names and no claim table names, and a sweep linking an artifact late adds claims without adding a blob. The memo's store prefix now also counts the rids the claim tables name at or below its top (claimRows; distinct rids, so a check-in's propagated tags do not move it), which settled checks before extending, and an extension takes as file content the old versions the arrivals' mlink rows name that no claim table names, as it does with the record. loadFiles' comment now says what the fallback decides rather than claiming a proof: an unlinked blob that parses as an artifact is the file mlink says it is, elided outside the cone, and a corrupt out-of-cone version is elided unread. Tests pin both. check-in: 40b018695a user: [email protected] tags: main, perf/cone-clone-rounds, trunk | |
| 12:58 |
docs(changelog): measure the carried chains over a classified store
With the store classified, the cone clone of fossil-scm.org's src takes 8 rounds and receives 57,041 blobs to hold 56,262, against 12 rounds and 83,521 without the carried chains. The chains also cover versions a merge check-in brings in from a branch, which made up most of the remaining repeats. check-in: 102dab787b user: [email protected] tags: main, perf/cone-clone-resend, trunk | |
| 12:52 |
seedcheck: keep deltas on a dropped seed readable; make a drop atomic
Review follow-ups to the seed drop. A delta that arrives on a seed already dropped could never be read: it was stored on a content-less row that is never filled or asked for. It is now not stored as a delta but left a phantom and asked for again in full, on a pull, a clone's phantom pull and a push to an origin. A seed that arrives in full after deltas built on it is stored just long enough to check those deltas, judge them and rewrite them as full text, and then dropped. seedcheck.Drop runs in a savepoint (or a transaction of its own on a pool), so a failure partway, which the after-dephantomize cascade logs and commits past, leaves the seed and its dependents as they were. An origin answering a push no longer asks for a refused seed's phantom, and a clone asking for what it left missing no longer counts one. fossil 2.28's rebuild re-adds every size<0 row to the phantom table and deletes the store-local seedrefused and seedpending tables; the CHANGELOG notes this and the one-time drop of refused seeds that older stores kept. The client now adds to a reply's new-phantom count rather than replacing it, so a phantom made while receiving a file continues the exchange. leaf check-in: 41c092205e user: [email protected] tags: fix/seed-delta-drop, main, trunk | |
| 12:47 |
fix(content): classify a fossil-written store's file versions from its tables
A store this library crosslinked records each blob it found to be no artifact, and classification skips the file versions among them. A store fossil wrote has no such record, so classification read every file version raw and exceeded its byte budget: on a copy of fossil-scm.org's repository it failed on every request. A cone clone's walk then kept no base it had not reached yet, sent every in-cone version whole, and fit two or three 2-4 MB sqlite3.c texts into a 16 MB round: about 390 rounds and 30 minutes, where a dense clone takes 8 rounds. Without the record, a version that mlink names and no table an artifact's crosslink writes claims is file content too, the proof the cone walk's knownFile already relies on; a table the store lacks claims nothing. That clone now takes 12 rounds and 9 minutes, verifying the same. Related to #465. check-in: 4536dae44c user: [email protected] tags: main, perf/cone-clone-rounds, trunk | |
| 11:59 |
perf: carry a cone clone's forwarded delta chains across rounds
The server's cone walk sends an in-cone file version as a delta with the chain of newer versions ahead of it as its bases. Each round restarts the walk from the clone's cursor, so every later round that reached one of those versions at its own check-in sent it again. The walk's cookie now names each chain a send forwarded: its head, length and an FNV-64 checksum of its rids. The next round rebuilds the chain from the store's delta table, counts its rids as sent, and drops it when the checksum no longer matches, so a store re-deltified mid-clone can never pass off a base the client was not sent. A run loses the leading rids the walk reaches, and the cookie carries at most 1,024 runs; one dropped is only sent again. On a copy of fossil-scm.org's repository a cone clone of src now receives 72,835 blobs to hold 56,262 (main: 100,212); the clones verify identically. check-in: 9d266a0f51 user: [email protected] tags: main, perf/cone-clone-resend, trunk | |
| 11:11 |
seedcheck: drop a seed completed as a delta in a later round
A horizon seed stored as a delta whose base arrived only in a later round (pull, clone) or a later request (origin push) was refused by its hash but kept in the store, and a pull or clone counted the delta as received on arrival, before it could be judged (draft-fossil-horizon-00 §5.2.3, HZ-5.2-14). On a seed verdict seedcheck now rewrites each delta built on the seed as full text, then empties the seed's row: size -1, no content, no delta link, and kept out of the phantom table so no request asks for it. The rid stays held for anything a receive pass tracks by it, and the refused hash is still recorded in store-local state. A pull or clone counts a delta it could not read on arrival only at the end of the exchange, once readable and not dropped. A round still treats it as progress. The receive pass treats a row without content as no basis even when its cache holds the old bytes, and the push handler's same-request seed check consults the refused hash, since the dropped row no longer reads. HZ-5.2-14 is now covered. check-in: 917bcc9830 user: [email protected] tags: fix/seed-delta-drop, main, trunk | |
|
2026-10-26
| ||
| 14:45 |
Merge pull request #513 from danmestas/test/organize-root-tests
test: move black-box tests to apitest/, consolidate root tests by source file check-in: b4ac68be08 user: [email protected] tags: main, trunk | |
| 14:36 | Merge remote-tracking branch 'origin/main' into test/organize-root-tests check-in: 2bb9dc608b user: [email protected] tags: main, trunk | |
| 14:36 |
Merge pull request #514 from danmestas/ci/cache-release-downloads
ci: cache the pinned Fossil and zit release tarballs check-in: 38eb1f810c user: [email protected] tags: main, trunk | |
| 14:28 |
ci: cache the pinned Fossil and zit release tarballs
fossil-scm.org answered 503 for the Fossil release tarball, and both test jobs failed before any test ran. The checksummed tarballs are now cached by version and checksum, so fossil-scm.org and zit's server are needed only on a cache miss; a cached copy is still verified before it is installed. check-in: b65ca240f1 user: [email protected] tags: main, trunk | |
| 14:05 |
test: consolidate root white-box tests by source file
Merge the root package's white-box test files so each mirrors the source file it exercises (checkout, fossil, horizon, repo_checkout, repo_history, repo_merge, repo_sync, repo_ticket, repo_extras, required_content, transport), and rename single files to match their source (repo_content_cache_test.go, repo_admin_test.go, required_content_checkout_test.go). Test bodies are unchanged; only imports are consolidated. check-in: af6431af27 user: [email protected] tags: main, trunk | |
| 14:00 |
test: move black-box root tests to apitest/
Every package libfossil_test file in the root used only exported identifiers and shared no helpers with the white-box tests, so all 21 move to a test-only apitest package. Docs that name their location follow. check-in: 6956a92661 user: [email protected] tags: main, trunk | |
| 13:09 |
Merge pull request #512 from danmestas/feat/opt-in-ssh-verify
feat(sshverify): make in-process SSH signature verification opt-in check-in: 5e7ce934cd user: [email protected] tags: main, trunk | |
| 13:00 |
Merge remote-tracking branch 'origin/main' into feat/opt-in-ssh-verify
# Conflicts: # CHANGELOG.md check-in: 52d7948375 user: [email protected] tags: main, trunk | |
| 12:59 |
test: guard simio against linking SSH crypto too
simio is the third package an embedder links; the opt-in guard now checks it alongside the library and db. check-in: d79a35c16a user: [email protected] tags: main, trunk | |
| 12:49 |
Merge pull request #511 from danmestas/feat/wasm-tickets
feat: ticket writes, ticket reads and timeline-since check-in: e448e2e48d user: [email protected] tags: main, trunk | |
| 12:45 |
feat(sshverify): make in-process SSH signature verification opt-in
internal/clearsign keeps the SSH envelope, SSHSIG parsing, namespace, hash and SHA-1 checks, none of which need cryptography, and hands the key parse and signature check to an SSHVerifier that the new public package sshverify registers from init. The library therefore no longer links golang.org/x/crypto/ssh and the ECDSA, P-256, Ed25519 and RSA code it pulls in; html-artifact-publisher's TinyGo engine drops from 1,347,569 to 1,093,647 bytes gzipped (3,777,936 to 3,208,411 raw). Without the verifier nothing is accepted unchecked: a well-formed SSH signature is ErrSSHVerifierUnavailable, which wraps ErrCannotVerify, so a seed under an SSH horizon-seed-key is refused, the held one is kept, and the sync or rebuild fails naming the package to import. Malformed signatures are still bad. The cli package (and so cmd/libfossil) and examples/tinygo import sshverify; a go list guard keeps x/crypto/ssh out of the root and db closures. check-in: a87790f468 user: [email protected] tags: main, trunk | |
| 12:39 |
Merge remote-tracking branch 'origin/main' into feat/wasm-tickets
# Conflicts: # CHANGELOG.md check-in: 1d8b2e6658 user: [email protected] tags: main, trunk | |
| 12:38 |
Merge pull request #510 from danmestas/fix/create-unsupported-stat
fix: let Create proceed when the filesystem cannot stat the parent check-in: b7e00e1673 user: [email protected] tags: main, trunk | |
| 12:30 |
fix: let Create proceed when the filesystem cannot stat the parent
Create's parent-directory check, added in #457, refused a path whenever os.Stat of its parent failed. Under TinyGo wasm a repository lives in a SQLite VFS and os.Stat is unsupported, so every create failed with "parent directory does not exist: /"; standard Go's js/wasm build hung on the same check. It now refuses only a parent the filesystem confirms is missing or is not a directory, and otherwise lets SQLite's own open report a real failure (tracked in #509). check-in: 2449bcd313 user: [email protected] tags: main, trunk | |
| 03:24 |
fix: timelineSince means arrived after, not stamped after
A sync handler asks what a push brought in. Filtering on timestamps missed an artifact made on a machine whose clock runs behind the given check-in's. TimelineSince now keeps entries whose artifacts were stored after the given one: a later receipt (blob.rcvid, which a phantom fill updates), then a higher rid. Any held artifact hash is accepted; an unknown one is an error. Tracked in #504. check-in: 29b53e75d2 user: [email protected] tags: main, trunk | |
| 03:14 |
feat: ticket writes, ticket reads and timeline-since
Repo.TicketAdd and Repo.TicketSet write the ticket-change artifact stock `fossil ticket add` / `fossil ticket set` write (D, J incl. +append, K, U, Z), byte for byte, and crosslink it in the same transaction so the ticket, ticketchng and event rows read back at once. TicketOpts.Comment is stored as the stock ticket forms store a remark: icomment with login, username and mimetype. Repo.TicketList, Repo.TicketChanges and Repo.TimelineSince expose the reads the html-artifact-publisher engine needs, as JSON-ready values. manifest.Timeline gains Kinds and Since to back TimelineSince. Tracked in #503 and #504. check-in: 68f2c3d930 user: [email protected] tags: main, trunk | |
| 02:38 |
Merge pull request #508 from danmestas/release/v0.16.2
chore(release): v0.16.2 check-in: 89f09bf179 user: [email protected] tags: main, trunk, v0.16.2 | |
| 01:42 | chore(release): v0.16.2 leaf check-in: 0da8ac4f50 user: [email protected] tags: release/v0.16.2, trunk | |
| 01:40 |
Merge pull request #507 from danmestas/test/horizon-partials
test(conformance): close four partial horizon rows check-in: 94717e5803 user: [email protected] tags: main, trunk | |
| 01:19 | Merge remote-tracking branch 'origin/main' into test/horizon-partials check-in: 825a901dda user: [email protected] tags: fix/conformance-subtest-t, main, trunk | |
| 01:18 |
test(conformance): close four partial horizon rows
HZ-5.2-2: the seed label appears on no artifact an origin or its structural clone stores, across re-seeding and writes on both sides; the minted floor survives a restart of the origin. HZ-5.2-9: a seed's missing base filled by a pull, a local check-in and a local tag leaves the seed refused, unlinked and unserved, and the transfer that filled it linked. HZ-6.1-2: path-cone and cursor pragmas beside horizon-name are answered as beside pragma horizon, and act; a named clone with path members sends them and records both. HZ-6.2-17: an event-mtime tie resolves to the lowest full hash for tip and a branch; a full-hash-shaped name no check-in has resolves as a symbolic name, and one a check-in has names that check-in first. check-in: 7a375b6d73 user: [email protected] tags: fix/conformance-subtest-t, main, trunk | |
| 01:07 |
Merge pull request #505 from danmestas/fix/conformance-subtest-t
test(conformance): hand each case its subtest's t; cancel stale Race runs check-in: 49bf242db8 user: [email protected] tags: main, trunk | |
| 00:59 | Merge remote-tracking branch 'origin/main' into fix/conformance-subtest-t leaf check-in: e0dcd0ea23 user: [email protected] tags: fix/conformance-subtest-t, main, trunk | |
| 00:59 |
test(conformance): hand each case its subtest's t; cancel stale Race runs
check runs each case as a subtest, but the suites passed the case the parent's t, captured in a closure. A fixture that skips or fails, as the fossil-built R-card fixture skips when fossil(1) is absent, then called SkipNow or FailNow on the parent from inside the subtest, which Go fails as "subtest may have called FailNow on a parent test". The Race workflow installs no fossil and failed this way in TestSparseCone. check now hands conforms the subtest's t, and all 150 call sites take it, so a case's skip or failure ends that case alone. TestCheckSkipEndsOnlyTheCase guards the harness. A Race run takes most of an hour and merges land faster, so a new run on a ref now cancels the older one. check-in: 43df2fd2c2 user: [email protected] tags: fix/conformance-subtest-t, main, trunk | |
| 00:54 |
Merge pull request #502 from danmestas/docs/sdk-reference-check
docs: regenerate the SDK reference and check it in CI check-in: 51d3b9cf5f user: [email protected] tags: main, trunk | |
| 00:46 |
docs: regenerate the SDK reference and check it in CI
The gomarkdoc SDK pages had drifted about a thousand lines from the code: make docs-gen-sdk was never part of docs-gen, so nothing regenerated them and the docs check did not compare them. ReadBlob, SetContentCache, ListFilesIn and OriginNamer were missing from the libfossil page. scripts/gen-sdk-docs.sh now renders them, running gomarkdoc v1.1.0 through go run so CI needs no install, then adds the frontmatter. It honours OUT_DIR, gen-docs.sh calls it, and check-docs-generated.sh compares the result, so a stale SDK page fails CI like any other generated input. make docs-gen-sdk calls the same script. llms.txt and llms-full.txt are regenerated from the refreshed pages. check-in: c12b02189f user: [email protected] tags: main, trunk | |
| 00:11 |
Merge pull request #501 from danmestas/perf/submanifest-node-memo
perf(manifest): memoize parsed sub-manifests by hash check-in: d82fddda6a user: [email protected] tags: main, trunk | |
| 00:02 |
perf(manifest): memoize parsed sub-manifests by hash
draft-fossil-submanifests-00 §7.4: enumeration results SHOULD be cached keyed by artifact name, so a rebuild parses each distinct sub-manifest once. The tree walk's loader now serves a resolved node from, and keeps it in, a new byte-bounded LRU memo on content.Cache (DerivedSized), keyed by the node's hash and charged its bytes, within a quarter of the cache's budget. The existing 32-entry Derived FIFO is sized for a few parsed check-ins; a tree's working set is every distinct node, which would thrash it and evict those check-ins. Only a node that resolved is kept, so a phantom is looked up afresh and fails the walk as before. Depth, cycle and node-count bounds stay per walk. The memo is dropped with Derived on Invalidate and Clear, and the Repo's cache is replaced at every write. Listing a tree that differs from one already read in one leaf now parses its manifest and that leaf; listing every check-in of zit's recorded tree history parses 48 sub-manifests instead of 109. SM-7.4-1 is covered. Tracked in #493. check-in: 94f61db228 user: [email protected] tags: main, trunk | |
| 00:01 |
Merge pull request #500 from danmestas/feat/list-files-in-cone
feat: list a check-in's files in a cone check-in: e90c972c40 user: [email protected] tags: main, trunk | |
|
2026-10-25
| ||
| 23:53 |
feat: list a check-in's files narrowed to a cone
Repo.ListFilesIn passes the public Cone to the enumeration seam as the range predicate (draft-fossil-sparse-cone-00 §4.1). On a tree manifest the walk skips sub-manifests the cone cannot reach (§4.5), so over a sparse store it succeeds where ListFiles fails on an elided node. It reads through the attached content cache, via a new internal manifest.ListFilesFilteredCached. Tracked in #491. check-in: 7156492179 user: [email protected] tags: main, trunk | |
| 23:44 |
Merge pull request #499 from danmestas/ci/race
ci: run the unit suite under -race; give packages 10m check-in: 612977a2d6 user: [email protected] tags: main, trunk | |