50 descendants and 50 ancestors of 6956a92661414c02
|
2026-10-29
| ||
| 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 | |
| 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:53 |
Store blobs over a hosted value limit across blobpart rows
A hosted SQLite that caps value size (a Durable Object's 2 MB) cannot hold a large file's stored blob in one blob.content value. With the new db.DriverConfig.MaxValueBytes set, a stored value over the limit is written in 1,000,000-byte parts to blobpart(rid, seq, content), created on first use, leaving blob.content NULL and blob.size the expanded length, so phantoms (size < 0) stay unambiguous. Chunking sits below compression: hashes, deltas and the wire format are unchanged. internal/blob owns the seam (ReadStored, WriteStored, InsertStored, StoredHead, DropStored, SweepParts and two scan expressions), and every direct reader or writer of blob.content now goes through it: delta rewrite and undelta, receive, clone send and walk, compaction, sparse demotion, seed drop, shun purge, horizon cost and cone preparation. Zero, the default, keeps every value inline and creates no table, so local stores stay byte-identical. A store with blobpart rows is not readable by stock fossil; enable it only for hosted backends. Closes #459 check-in: 1fd12b3ed3 user: [email protected] tags: feat/chunked-blobs, 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: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 | |
| 23:37 |
ci: run the unit suite under -race; give packages 10m
A Race workflow runs every unit package under the race detector after each merge to main, nightly, and on demand. The full suite passes under -race with no data race reported; it takes several times as long as without, so it does not gate pull requests, which already must be up to date with main to merge. Fossil and zit are not installed there; their interop tests skip, and the Tests workflow runs them. The unit steps' -timeout is per package binary, and conformance alone takes 130-255s on a hosted runner, so 300s failed it on runner variance. It is now 10m. The dst steps keep 300s. check-in: fdd361b5a9 user: [email protected] tags: main, trunk | |
| 23:36 |
Merge pull request #498 from danmestas/fix/horizon-cursor-embedder-transport
fix(sync): export OriginNamer so an embedder's transport keeps a horizon cursor check-in: a52911df25 user: [email protected] tags: main, trunk | |
| 23:29 |
fix(sync): export OriginNamer so an embedder's transport keeps a horizon cursor
Sync and Clone keyed the horizon cursor (draft-fossil-horizon-00 §6.2.6) on an unexported syncOrigin method, which only the HTTP transport could satisfy. Any other Transport kept no cursor and announced its whole holding with igot on every pull. OriginNamer is an optional interface, discovered by type assertion like FramedTransport, with SyncOrigin() string. The HTTP transport implements it in place of the unexported method, so one assertion serves both. The name must identify the peer stably and carry no credential. Tracked in #488. check-in: 8d16b7289c user: [email protected] tags: main, trunk | |
| 23:28 |
Merge pull request #496 from danmestas/feat/read-blob-content-cache
feat: read an artifact by hash and cache content on Repo check-in: 5e882bf1f1 user: [email protected] tags: main, trunk | |
| 23:10 |
feat: read an artifact by hash and cache content on Repo
Repo.ReadBlob expands one artifact by its full hash after the availability check ReadFile applies, and names an unknown hash with ErrArtifactNotFound. Repo.SetContentCache attaches a byte-bounded content.Cache that ReadFile, ReadFileAt, ReadBlob, ListFiles, Diff and Annotate share; check-in parses are kept in its derived memo, so reads at one check-in parse the manifest once. Default off. A read that finds the DB's finished-write count moved swaps in an empty cache, so a purged, compacted or elided artifact, and a rid SQLite reuses after a purge, is never served from it. Writes by another process are not seen; the method's doc says so. Tracked in #492. leaf check-in: d367012009 user: [email protected] tags: feat/read-blob-content-cache, trunk | |
| 23:10 |
feat(db): count finished writes
DB.Writes counts pool Exec calls that returned and WithTx transactions that ended. Counting at the end is what lets a reader holding state across calls treat an unchanged count as no write having landed in between. check-in: 51be37dc1a user: [email protected] tags: feat/read-blob-content-cache, trunk | |
| 23:10 |
Merge pull request #497 from danmestas/ci/gofmt
ci: check gofmt; format the tree check-in: 969c8f1fe4 user: [email protected] tags: main, trunk | |
| 23:02 |
ci: check gofmt; format the tree
Sixteen files had drifted from gofmt: struct and comment alignment, one-line if bodies in auth_test.go, trailing blank lines, and a blank comment line gofmt now puts before an indented block in two delta.go doc comments. The test job now fails on any tracked .go file gofmt would change, and lists it. check-in: b2dcbb7130 user: [email protected] tags: main, trunk | |
| 22:36 |
Merge pull request #495 from danmestas/ci/zit-0.7.3
ci: pin zit interop to 0.7.3 check-in: 95610ffc23 user: [email protected] tags: main, trunk | |
| 22:27 |
ci: pin zit interop to 0.7.3
zit 0.7.3 (version-0.7.3, check-in f18e3cc041) carries draft check-in 39546186f4, the minted seed floor of horizon §5.2.2 that go-libfossil implements since #485, so the pinned interop zit and the pinned draft copy agree again. Every zit interop test passes against it, none skipped, with REQUIRE_ZIT_BIN and REQUIRE_ZIT_SEED_DATE set. check-in: 10737ceaec user: [email protected] tags: main, trunk | |
| 22:23 |
Merge pull request #494 from danmestas/feat/carried-uuid-default-login
feat(commit): carry files by UUID; fix Create's default login check-in: d0abc1b3fa user: [email protected] tags: main, trunk | |
| 21:08 |
fix: validate carried hashes and check FOSSIL_USER first
A carried file outside a sparse store's cone skips the held check, so any caller text of 40 bytes or more reached the F card verbatim, including a newline that writes a card of its own into the check-in. checkCarried now refuses a UUID that is not a lowercase SHA1 or SHA3-256 hash, which covers FileToCommit.UUID and the sparse checkout path alike (tracked in #486). The default login follows fossil's db_create_default_users, which checks FOSSIL_USER before USER, LOGNAME and USERNAME (tracked in #487). leaf check-in: 2237a3781a user: [email protected] tags: feat/carried-uuid-default-login, main, trunk | |
| 21:05 |
fix(create): seed fossil's fallback login when no user is configured
Create with an empty CreateOpts.User read only $USER and then panicked in db.SeedUser on the empty login whenever that variable was unset, as it is in many containers and CI runners. The default login is now chosen as fossil(1)'s db_initial_setup chooses it: the first of USER, LOGNAME and USERNAME that is set, else "root". check-in: c15a6c60e9 user: [email protected] tags: feat/carried-uuid-default-login, main, trunk | |
| 21:05 |
feat(commit): carry a file into a check-in by UUID without its content
FileToCommit gains a UUID field. When set, the F card names that artifact verbatim and nothing is read or stored, reaching the internal manifest.File.UUID path that sparse checkouts already use. A caller that lists a parent's files can now commit with PartialManifest, dropping files, by naming the unchanged part of the tree instead of re-reading and resubmitting every byte of it. Both UUID and Content on one file is refused; so is a UUID the store does not hold outside an eliding cone. check-in: 37bba3f7b9 user: [email protected] tags: feat/carried-uuid-default-login, main, trunk | |
|
2026-10-24
| ||
| 19:19 |
Merge pull request #485 from danmestas/feat/horizon-seed-floor
fix(horizon): date a seed no earlier than the origin's minted floor check-in: d47958493b user: [email protected] tags: main, trunk | |
| 19:04 | fix(horizon): read a NULL minted floor as none and replace it leaf check-in: 387b9de549 user: [email protected] tags: feat/horizon-seed-floor, trunk | |
| 19:03 |
fix(horizon): keep the minted floor best effort
A mint wrote the floor on every structural pull and failed the request when it could not, so a read-only mirror or a store whose write lock was held no longer served the structural horizon. The floor is now written only when D moves later, and a failed write is ignored: the mark is a SHOULD, and without it the next seed is dated from the tag rows as before. A stored value that is not a canonical integer, which an overflowing cast would rank above every D, is replaced. check-in: 8356d3d19e user: [email protected] tags: feat/horizon-seed-floor, trunk | |
| 18:56 |
fix(horizon): date a seed no earlier than the origin's minted floor
Shunning and purging the latest tag event removed the tagxref rows a seed's D was dated from, so the next seed was earlier than the one every structural clone held. Each clone's D floor refused it as a replay and it fell back to the content horizon until the repository's tags caught up with the purged tag's time; a far-future tag held it there indefinitely. The draft (zit 39546186f4, §5.2.2) now has an origin date a seed no earlier than the latest D it has minted and keep that D across restarts. Mint reads the mark from the store's horizon-seed-floor config row, dates the seed at the later of it and the latest tag event, and raises the row after minting with a conditional upsert, so concurrent mints never lower it. No config group transfers the row, so it is never sent. Re-pins draft-fossil-horizon-00 at 39546186f4 and adds the conformance case horizon.5.2.2.seed-date-shun, which fails without the floor. check-in: 1fd0dfdbc1 user: [email protected] tags: feat/horizon-seed-floor, trunk | |
| 18:36 |
Merge pull request #484 from danmestas/release/v0.16.1
chore(release): v0.16.1 check-in: 4ac6baf2ea user: [email protected] tags: main, trunk, v0.16.1 | |
| 18:28 | chore(release): v0.16.1 changelog check-in: 08d681d6c3 user: [email protected] tags: main, trunk | |
| 16:47 |
Merge pull request #482 from danmestas/feat/horizon-dfloor-receive
feat(horizon): re-pin to zit caeef421ee: repository-wide seed D and the receive rule check-in: 7b6f43f1dc user: [email protected] tags: main, trunk | |
| 16:39 |
ci: pin zit interop to 0.7.2 and require the seed-date test
zit 0.7.2 (tag version-0.7.2, check-in 1dcb2d4a29) dates a seed at the repository's latest tag event, as go-libfossil now does. Its horizon draft is the caeef421ee text pinned here (artifact ab18fa7f89). - ZIT_VERSION is 0.7.2, and ZIT_SHA256 is the checksum of zit-0.7.2-linux-x86_64.tar.gz, checked against the release's SHA256SUMS. - REQUIRE_ZIT_SEED_DATE=1 sits next to REQUIRE_ZIT_BIN, so TestHorizonSeedDateZit cannot skip in CI. - The README, horizons.md, CHANGELOG and test comments now say interop is pinned to zit 0.7.2. check-in: 0c30e0d32e user: [email protected] tags: feat/horizon-dfloor-receive, main, trunk | |
| 14:45 |
test(conformance): seed byte-identity with zit over a cancelled tag
TestHorizonSeedDateZit has zit mint the seed for a repository whose newest tag event is a cancel, and requires it to match go-libfossil's byte for byte. Both now date a seed at the repository's latest tag event (§5.2.2). No string in the binary tells a zit from before caeef421ee apart: its version still reads 0.7.1. So the test recognises an older zit by an earlier D in an otherwise identical seed, and skips it unless REQUIRE_ZIT_SEED_DATE=1. CI's pinned zit 0.7.1 skips. A zit built at caeef421ee passes with the variable set. leaf check-in: ad82b4a46d user: [email protected] tags: feat/horizon-dfloor-receive, main, trunk | |
| 14:34 |
Merge feat/seed-receive-drop; re-pin horizon draft to zit caeef421ee
Re-pins draft-fossil-horizon-00 to zit trunk check-in caeef421ee (file artifact ab18fa7f89), which changes §5.2.2, §5.2.3 and §6.2.3 and adds two Appendix C bullets. This brings in the local feat/seed-receive-drop branch, which implements the receive rule, and resolves it against main (#477 chain-walk bounds, #478 seed binding). §5.2.2, the seed's D (tracked in #479): - Mint dates a seed at max(tagxref.mtime) over every row in the repository, cancels and propagated rows included, as zit's latestTagEvent does. It was the latest of the rows the seed summarizes. - A cancel of the newest tag, or a reopened closed branch, therefore no longer mints a seed earlier than the one a clone holds, which the clone's D floor refused as a replay. §5.2.3, the receive rule: - A pull, clone or sync drops a seed by this store's label or by a binding card when it can read it, and the link step refuses one completed later. - The draft also requires that a delta kept without its base, pushed or received, is not announced or served while it cannot be expanded. feat/seed-receive-drop only withheld pushed ones. The pending mark moves into storeDeltaContent, so a pull marks too, and a clone marks what is still unreadable when it ends (seedcheck.WithholdUnreadable). That avoids judging every dangling file delta of a clone a second time. - Since #478 minted seeds carry a binding card. The seed-shaped-ordinary cases therefore strip it, and a new test checks that a seed with the card is never linked. Conformance rows: - HZ-5.2-2 takes the new D text. - HZ-5.2-4 loses the old ordinary-artifact sentence. - HZ-6.2-6 says the D is fixed by the repository's tag events. - New rows: HZ-5.2-14 (the receive-path drop; PARTIAL, since a seed completed in a later round is kept inert, and was counted when its delta arrived), HZ-5.2-15 (the dropped-hash record) and HZ-5.2-16 (withholding unexpandable deltas). - New cases: two D cases mirroring zit's "horizon seed date" regression, a cancel on C and a cancel at an ancestor below it, and five receive cases. - The deepen-floor case now replays an older seed, since an honest origin no longer mints one. check-in: 5d0ef566aa user: [email protected] tags: feat/horizon-dfloor-receive, main, trunk | |
| 13:58 |
Merge pull request #481 from danmestas/release/v0.16.0
chore(release): v0.16.0 check-in: 2f1e5f1e3d user: [email protected] tags: main, trunk, v0.16.0 | |