Skip to content
go-libfossil Timeline
Login

Timeline

19 descendants and 50 ancestors of 4069607141b9c606

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: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

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: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: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
22:44
perf(content): read trees lazily again, kept in the compact encoding

A full read of every tree with the graph made a cold classification on fossil-scm.org's clone three to four times slower, which an origin that starts cold often cannot afford. Walks read a tree when they first need it, as before, and the memo keeps what they read as a treeIndex (interned ids, delta-varint sets, full manifests as differences), replaced on each publish so views read it without the lock. memoTreeBytes now bounds that encoding, which holds the whole history of fossil-scm.org in about 11 MiB; past it the index starts again. HZ-6.2-10 stays partial: trees are still read on first use, not with the graph. check-in: b53551c192 user: [email protected] tags: main, perf/memo-tree-encoding, trunk

22:41
fix(horizon): place the held set once per read; annotate falls back when the peer is short

- manifest.Placement computes the horizon's held set at most once per operation, so an annotate walk or a whole-check-in diff no longer rebuilds the check-in graph on every failed read. - AnnotateContext whose peer cannot supply a version returns the partial result, the lines past C marked BelowHorizon, as Annotate does without a peer; only a cancelled context fails it. - AnnotatedLine documents that BelowHorizon lines carry C's identity and that a partial annotate is signalled only by those marks. check-in: 02a2ea3214 user: [email protected] tags: feat/demand-fetch-reads, main, trunk

22:23
fix(horizon): label only absences below C; annotate fetches what it walks

Review follow-ups on the demand fetch for reads below the horizon:

- A read is refused as below the horizon, and demand-fetched, only when its check-in lies outside the held set K; an unheld payload above C, such as one outside the store cone's paths, keeps its own error and is not fetched. - Annotate no longer refuses without a peer: on a content-horizon store it reports the lines it cannot attribute past C at the horizon, marked BelowHorizon, as a structural store does. AnnotateContext fetches each version as the walk reaches it, so nothing the walk does not read is fetched, up to 200 payloads; past that it returns the partial result. - diffWholeCheckin reports a failed structure classification, and the read paths take the caller's context through to the fetch. - Tests: misclassification above C, annotate over-fetch, the partial result over the limit, and a structural peer withholding the payload. check-in: 52f026832e user: [email protected] tags: feat/demand-fetch-reads, main, trunk

21:55
feat(horizon): targeted demand fetch for reads below the horizon

ReadFileContext, ReadFileAtContext, AnnotateContext and DiffContext take RequiredContentOpts and, when a read needs content a horizon store does not hold, fetch exactly the artifacts it names from the peer and read again (draft-fossil-horizon-00 4.4): the payload, and on a structural store first the check-in's manifest, baseline and nodes. The store cone and its horizon do not move; fetched artifacts are held content.

Reads now refuse with content.MissingBelowHorizon, which matches ErrBelowHorizon and names the horizon. Annotate gathers every missing version along its walk into one round and Diff both sides; a read over one round's gimme budget is refused naming DeepenHorizon.

Closes #489 leaf check-in: 8821596379 user: [email protected] tags: feat/demand-fetch-reads, main, 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:26
perf(content): read the memo's trees with its graph, as interned id sets

The origin classification memo now reads the F cards of every tree in the store once, with its references, and extends them by appended check-ins (draft-fossil-horizon-00 §6.2, HZ-6.2-10). Blob names are interned to uint32 ids, each tree's file set is a sorted delta-varint id list, and a full manifest is kept as its symmetric difference from the previous full manifest read, at most 15 links from a piece kept whole, decoded through a 16-entry LRU per view. A store whose index would pass memoTreeBytes keeps none and falls back to the bounded cache of walked trees.

On a clone of fossil-scm.org the trees take 11 MiB (the walked-tree cache held 50 MiB for two horizons), the memo's retained heap halves and peak in-use heap drops from 345-524 MiB to about 170 MiB. Tracked in #463. check-in: 1cb9e11a65 user: [email protected] tags: main, perf/memo-tree-encoding, 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