50 descendants of 1bcbee0f86aac90e
|
2026-07-18
| ||
| 22:45 |
Implement add/rm directory recursion through the shipped extras.sweep walk
`zit add DIR` and `zit rm DIR` were hard errors that told the user to name individual files. That made the two most common invocations -- `zit add .` and adding a source subtree -- dead ends, and it was the last place where the CLI asked the user to do work the repository already knows how to do. What changed: - `add DIR` now expands to every untracked file beneath the directory. It reuses the ALREADY SHIPPED `extras.sweep` of the checkout root rather than introducing a second walker: the same walk, the same exclusions, the same no-follow symlink posture `addremove` and `clean` use. The sweep is lazy and memoized for the whole invocation, so `zit add a b c` over three directories still performs exactly one enumeration. Because the sweep already subtracts the tracked set and the depth-0 control names, neither can be re-inserted or re-reported by the directory path. - `rm DIR` expands over the TRACKED keys via `ManagedSet.iterate`, never over the disk, so a directory the user has already deleted is still forgettable. Keys are copied into the arena before the first removal, because `iterate()`'s slice and its backing bytes are invalidated by `remove` -- the same discipline `addremove` uses for its remove set. Ascending order is preserved and every expanded key still passes the per-key control-file guard (B-rm-7). - One containment predicate, `pathUnderDir`, serves both verbs, so the boundary case cannot diverge between them: `src2/c` is not under `src`, because containment requires the separator immediately after the directory's bytes. - No new diagnostic class. `zit rm .` with nothing tracked resolves to the existing not-tracked message rather than inventing an empty-expansion error; `zit add .` on a tree holding only control files adds nothing and succeeds. - The `add` and `rm` help text and exit-status wording now describe recursion instead of the removed directory error. Test evidence: the B-add-5 and B-rm-5 contract tests were rewritten to pin the new behavior (a named symlink is still rejected; a directory now recurses; `.` at the root is never reported as outside the repository), and a new P3-F4 block of end-to-end tests covers nested trees, prefix-boundary siblings, multiple directory arguments sharing one sweep, --dry-run, --hard, and rm of a directory that no longer exists on disk. `zig build test` passes in this worktree after absorbing the sibling packaging commit (3645c03e2d), and the commit is proved against a clean `fossil open` checkout running `zig build` then `zig build test`. check-in: 281c0ed815 user: dmestas tags: release-0.1 | |
| 22:13 |
Add scripts/mirror-export.sh: the reproducible export to the private mirror
The mirror is a derived view for running CI, never a source of truth — fossil is the repository, and every push is a replay of fossil history, which is why it pushes --mirror and nothing is ever committed git-side. Why a script when 'fossil git export' already works: its configuration (mirror directory and autopush URL) is per-checkout state, machine-local and invisible to anyone else cloning this repo. The script documents the one supported way to run the export and fails with the exact 'fossil git export ... --autopush ...' fix line when a checkout has never been configured. It also re-drives the push explicitly rather than relying on autopush. Autopush is best-effort: the first export of the CI commit hit a transient connection refusal to github.com, left the local mirror ahead of the remote, and 'fossil git status' still reported up-to-date — because that status describes the export, not the push. Re-driving makes the push's exit status the script's. The mirror is PRIVATE and stays private; the script never touches visibility. check-in: ef4efe16b3 user: dmestas tags: trunk | |
| 22:03 |
Add CI: private git mirror + GitHub Actions running the release battery
CI is the multiplier for the rest of the 0.1.0 sequence: from here every fix lands with fmt/build/test/e2e/dst/smoke gated automatically, and the smoke conformance claim — zit artifacts and deltas validated against the real fossil binary — becomes continuously enforced instead of milestone-manual. - .github/workflows/ci.yml: single ubuntu-latest gating job on push and PR. Zig 0.16.0 pinned by version AND sha256 digest, verified with sha256sum --check --strict before use; fossil installed for smoke; the battery runs in order with a 20-minute job ceiling and a 5-minute bound on smoke, which is the one step that can hang without producing output. - src/ci_test.zig: structural pins over the workflow, so the file cannot drift from what it claims. Each pin is anchored to a whole line with its YAML indentation — a loose substring is satisfied by the phrase appearing in a comment while the block that governs the run says something else. The digest pin names ZIG_SHASUM and the tarball together, so the digest this repo pins is provably the digest the workflow checks. - src/packaging_test.zig: a negative-space pin asserting every path build.zig embeds is version-controlled. This diff is why: build.zig came to depend on .github/workflows/ci.yml while that file was untracked, and bare 'fossil extras' does not list dotfiles, so the commit-discipline check reported clean. The pin runs 'fossil ls' and fails on any embed that is not tracked. The mirror is PRIVATE (github.com/danmestas/zit) and stays private; publishing is a separate decision that is deliberately not part of this release. check-in: 8a003b2ada user: dmestas tags: trunk | |
| 21:11 |
Merge release-0.1: Narrow .paths to docs/rfcs so working docs never ship in a fetched package
Second increment of the 0.1.0 release sequence, landed on trunk as it completes rather than accumulating to a big-bang merge at the end. The increment: build.zig.zon .paths named the whole docs directory. A Zig package is cut from the working tree, not from what fossil tracks, so the ignore-glob does not govern it -- a bare docs entry ships whatever sits under docs/ at package time, including the ignore-glob'd docs/plans/*.md working documents, which are internal and must never leave the repository. .paths now names docs/rfcs directly, which is also what the repo convention already states (0ad7fa92c6: "docs/ is for the RFCs only"); the manifest encodes that convention instead of relying on docs/ happening to stay clean. Evidence: a new pin in src/packaging_test.zig asserts both halves -- that .paths contains "docs/rfcs", and, as negative space, that a bare "docs" entry is absent, so the ship-everything form cannot drift back unnoticed. The merged tree was re-verified before this commit: zig fmt --check, zig build, and zig build test all green on the merge result. Green on the branch does not imply green on the merge, since trunk advances with concurrent work. No borrowed techniques in this change, so no attribution is owed. check-in: 0c22d3a950 user: dmestas tags: trunk | |
| 20:52 |
Narrow .paths to docs/rfcs so working docs never ship in a package
`.paths` named the whole `docs` directory. A Zig package is cut from the working tree, not from what fossil tracks, so the ignore-glob does not govern it: a bare `docs` entry ships whatever sits under docs/ at package time — including the ignore-glob'd docs/plans/*.md working documents, which are internal and must never leave the repository. Naming docs/rfcs directly is also what the repo convention already states (0ad7fa92c6: 'docs/ is for the RFCs only'); the manifest now encodes that convention instead of relying on docs/ happening to stay clean. A new pin in src/packaging_test.zig asserts it so the bare-docs form cannot drift back. check-in: 3645c03e2d user: dmestas tags: release-0.1 | |
| 20:30 |
Merge trunk into release-0.1: resync after the P1 land
P1 landed on trunk (8117eab3b2) per the continuous-integration rule, so the release branch absorbs trunk to keep a single lineage. Content-identical merge — this records the ancestry so later increments do not re-traverse it. check-in: 6d229acebf user: dmestas tags: release-0.1 | |
| 20:27 |
Merge release-0.1: BSD-2-Clause LICENSE and the packaging structural pins (P1)
First increment of the 0.1.0 release sequence, landed on trunk immediately per the plan's continuous-integration rule rather than accumulating to a big-bang merge at P6. - LICENSE at the repo root (BSD-2-Clause, copyright Daniel Mestas 2026). zit is an independent reimplementation from the Fossil RFCs, not a derivation of Fossil source, so the choice was open; BSD-2-Clause matches the Fossil heritage and the systems-Zig ecosystem. - build.zig.zon .paths names LICENSE, so it ships inside a fetched package. - The commit-description-* scratch file is gone and the pattern is in the versioned ignore-glob. - src/packaging_test.zig pins these as executable facts, including a negative-space pin asserting every repo-root path build.zig embeds is also named in .paths — the embeds attach to the test-only module, never to the exported zit module, so a consumer never depends on a file outside .paths. The merged tree was re-verified before this commit (fmt/build/test green): green on the branch does not imply green on the merge, since trunk advances with concurrent work. check-in: 8117eab3b2 user: dmestas tags: trunk | |
| 20:19 |
Remove docs/RELEASE-0.1.0-PLAN.md from tracking; working docs live under the ignored docs/plans/
The release plan is a working document, not a shipped artifact. Repo convention (0ad7fa92c6) is that docs/ carries the RFCs only, with docs/plans/* already in the versioned ignore-glob; the plan, the readiness assessment, and its earlier draft all belong there. P1 added the plan because its own scope text said to commit it — that instruction is wrong and has been removed from the plan. The file is unchanged on disk, now at docs/plans/RELEASE-0.1.0-PLAN.md. It remains in the history of 122998a428 — fossil history is immutable and shunning it would be disproportionate for a working doc. check-in: 3179b6805a user: dmestas tags: release-0.1 | |
| 20:12 |
Add BSD-2-Clause LICENSE, ship it in .paths, drop scratch file, ignore the pattern
A release without a license grants nobody any rights, so this is the legally gating step of the 0.1.0 sequence. zit is an independent reimplementation from the Fossil RFCs rather than a derivation of Fossil source, so the choice was open; BSD-2-Clause matches the Fossil heritage and the systems-Zig ecosystem. What changed: - LICENSE at the repo root, BSD 2-Clause, copyright Daniel Mestas 2026. - build.zig.zon .paths now names LICENSE, so the file actually ships inside a fetched package instead of only existing in the checkout. - The leftover commit-description-413190fb92be991f.txt scratch file is gone and .fossil-settings/ignore-glob carries commit-description-* so the class of file cannot drift back in. - src/packaging_test.zig pins the above as executable facts: LICENSE content, LICENSE named inside the manifest's brace-matched .paths block, a line-anchored ignore-glob pin, and a negative-space pin asserting every repo-root path build.zig embeds is also named in .paths. These acceptance criteria are file-structure facts, which rot silently because nothing executes them; the battery alone cannot catch a .paths regression. - build.zig grows build_unit_test_module: the pinned repository files attach as anonymous imports to a test-only compile of root.zig, never to the exported zit module. Attaching them to zit made every consumer's build depend on files outside .paths and a fetched package failed on a missing .fossil-settings/ignore-glob before compiling a line. The negative-space pin exists to catch exactly that recurrence. - docs/RELEASE-0.1.0-PLAN.md, the plan of record for the 0.1.0 release, is committed alongside the first step it describes. Test evidence: zig build and zig build test green in the working checkout. This commit is additionally gated on a clean-checkout proof — fossil open into a fresh temp directory, then zig build and zig build test there — which is the check that catches a file referenced but never added. No borrowed techniques in this change, so no attribution is owed. check-in: 122998a428 user: dmestas tags: release-0.1 | |
| 19:29 | Create new branch named "release-0.1" check-in: be86f4a5e6 user: dmestas tags: release-0.1 | |
| 17:47 |
storage-bench: add a dict-trained headroom arm (§2.2 preset-dictionary GO/NO-GO)
Extend the full-form (.zlib) compression-headroom probe with a dictionary sub-arm: train a candidate preset dictionary on the sampled full-form population and recompress the SAME frames with it preloaded, reporting the net byte gain over the empty-window baseline. This is the GO/NO-GO signal for adding a preset-dictionary frame class. At-rest MEASUREMENT only: it never touches the store or the production §4.1 codec, and it preloads the dictionary through a throwaway reference-difference method because Zig 0.16 std flate hardcodes FDICT=0 and exposes no setDictionary. The dictionary-compression lineage this arm measures: RFC 1950 reserves the FDICT bit and the 32 KiB preset-dictionary window, and its §9 says the most- referenced strings belong at the dictionary tail (shortest back-references); Yann Collet's zstd --train/COVER is the reference for training a shared dictionary from a population of small, mutually-similar records; Chromium productionised shared-dictionary transport at scale (SDCH, later the Compression-Dictionary-Transport work); Fabrice Bellard's LZEXE established the practical art of squeezing highly-repetitive input with a tiny back- reference machine. closed check-in: 1027cba83c user: dmestas tags: storage | |
| 17:21 |
Remove docs/plans/bench benchmark-run JSON from tracking; ignore docs/plans
These four bench-run outputs (baseline / step2-pickbase / step3-pack-redeltify / step4-samepath) were measurement artifacts committed under docs/plans/bench — they do not belong in version control (docs/ is for the RFCs only). Remove them from the tip and add docs/plans/* to the versioned ignore-glob so the pattern cannot recur. They remain in the historical check-ins that added them (9aaf485847 and the integration merges) — Fossil history is immutable; purging them from provenance entirely would require fossil shun, deliberately not done. check-in: 0ad7fa92c6 user: dmestas tags: trunk | |
| 17:12 | Merge trunk into storage: bring in the converged trunk (storage pack work + the cli verb slice — annotate, bisect, extras, merge3, undo, cherrypick/backout) so the dict lever (§2.2 preset-dictionary compression) builds on current state. check-in: 09748596dc user: dmestas tags: storage | |
| 17:08 |
Merge cli: the cli-verbs-2 survey slice — seven new verbs
Lands amend, artifact, bisect, log PATH, annotate, merge --cherrypick/--backout, and addremove — the VCS-verb-survey recommendations — onto trunk, already integrated with the storage branch's pack re-deltification. - amend: mint display-metadata T cards, never rewrite history (also corrects the git-reflex table's wrong immutability lesson). - artifact: the plumbing window (whatis + raw dump) over the one parser. - bisect: DAG binary search (bad/good/next/status/reset), state in .zit-bisect. - log PATH: per-file history folded into log (fossil finfo). - annotate: per-line provenance; the version chain is ordered by TOPOLOGY (matching fossil's generation walk), not mtime, so a parent is never misattributed to its child on a timestamp collision. - merge --cherrypick/--backout: alternate-baseline special merges recording a 4.7.14 Q card, rendered by info; the plain merge surface is byte-identical. - addremove: one sweep, add every extra and drop every missing. Built decomposed (focused implementers + adversarial review) after the monolithic workflow thrashed on API instability. 1678 unit + 376 e2e + dst 6x1000 green. check-in: e5fffafdb8 user: dmestas tags: trunk | |
| 17:05 |
Merge trunk into cli: integrate the storage pack work with the verb slice
Brings trunk bab930 (the storage branch's at-rest re-deltification / zit pack generational rewrite, plus src/pack.zig and its bench baselines) into the cli-verbs-2 slice. One conflict, in src/root.zig — both branches added a module export at the same spot; resolved by keeping both (pub const bisect and pub const pack). logstore.zig and main.zig auto-merged. Integrated tree green: 1678 unit + 376 e2e; the seven new verbs and the pack pass together. closed check-in: e640119891 user: dmestas tags: cli | |
| 16:58 |
info: render the cherrypick/backout provenance from the Q card (B-merge-41)
zit info on a cherrypick or backout check-in now shows a provenance line derived from the parsed 4.7.14 Q card: a 'cherrypick:' line for Q plus and a 'backout:' line for Q minus, each carrying the full REV hash, placed right after the parent and merge lines with the value column aligned. It reads through the shipped artifact parse path (repo.manifest then artifact.parse then parseQCard, iterating manifest.q) — no bespoke re-parse. A check-in with no Q card prints no such line and is byte-identical to before. Also fixes an allocator-symmetry bug uncovered here: renderInfo now threads gpa (was arena) so info's manifest frees against the same allocator, ending an Invalid free panic at repo.deinit on every info call. Closes the last deferred clause of the cli-verbs-2 slice. 1678 unit + 376 e2e green. check-in: 4b1be918ab user: dmestas tags: cli | |
| 16:44 |
annotate: order the version chain by topology, not mtime
An adversarial review found zit annotate could charge a line to the too-recent CHILD when a parent and child check-in share an identical (or non-monotone) event mtime — the chain was ordered (mtime asc, hash). Fossil orders its annotate walk by pure topology (generation number, descendants.c/diff.c), so a parent is ALWAYS processed before its child. Match that. New repostate.ancestryLinearized runs Kahn's over the reversed (child->parent) ancestry edges within the file's ancestor set, emitting newest->oldest with a (mtime desc, hash) ready-queue among topologically-INCOMPARABLE nodes, then reverses to the engine's oldest-first order — topology is now the PRIMARY key, mtime/hash only orders incomparable versions. annotateFile consumes it in place of the old sort; the attribute engine (primary-parent pass-through, oldest origin, binary refusal, layout) is unchanged. Amends the RESOLVED annotate-attribution rule. Linear/monotone history is byte-identical (degenerates to mtime-ascending); new tests (same-mtime parent-first, non-monotone, merge primary-parent) fail against the old order. 1678 unit + 376 e2e green. check-in: ff9662dfee user: dmestas tags: cli | |
| 16:24 |
Merge storage: at-rest re-deltification closes most of the content-bytes gap
fossil-style best-of-N delta base selection (pickBase, plan §5) plus a `zit pack` re-deltification pass with a monotonic-shrink guard (§6.1) and same-path candidates (§6.2). At-rest only: all four RFCs conformant, wire interop preserved, every repacked frame hash-verified against the log. Measured (whole-file, index-inclusive): sqlite: 207.3 MB -> 174.4 MB (1.40x -> 1.18x vs fossil 147.7 MB) fossil-scm: 117.5 MB -> 108.1 MB (1.63x -> 1.50x vs fossil 72.2 MB) The monotonic-shrink guard (keep the smaller of {existing frame, re-encoding}) is what turned re-deltifying a clone of fossil-quality wire deltas into a net win instead of a +33% regression. check-in: bab9303056 user: dmestas tags: trunk | |
| 16:23 |
Add the addremove command: one sweep, add every extra and drop every missing
zit addremove [--dry-run] — checkout-layer convenience (fossil/hg parity; git add -A). ONE scan via the shipped extras.sweep (the exact untracked/exclusion walk zit extras and zit clean use) is the ADD set; every managed file absent from disk (no-follow stat, as status checks) is the REMOVE set. Both mutate the managed set through the shipped ManagedSet.insert/remove/write (write-temp + fsync + rename) — no second walker, no ad-hoc mutation. A present-but-modified file is never touched; control/never-tracked names are never added. --dry-run leaves .zit-managed byte-identical and prints the same report (ADDED block then REMOVED block, each ascending, root-relative). 1675 unit + 374 e2e green. check-in: f0dc5f4e2a user: dmestas tags: cli | |
| 16:20 | fix(§6): track src/pack.zig — the pack re-deltification module was recorded by reference (root.zig import) but never fossil-added, so a clean checkout of the storage tip failed to build. Content unchanged; passed every §6 gate. check-in: e2a1514213 user: dmestas tags: storage | |
| 15:59 |
Add merge --cherrypick / --backout as special merges (no new verbs)
Both are alternate-baseline choices inside the shipped merge machinery, per merge-flags contract B-merge-30..40 (which lifts merge.md's v1 non-goal); the flagless zit merge NAME surface is byte-identical (B-merge-31 re-asserts it). - --cherrypick REV: three-way with base = REV's primary parent, other = REV, current = checkout (apply REV's own changes). --backout REV: base = REV, other = REV's primary parent (reverse REV). Same classifyFile/applyMerge engine; a root REV yields an empty side; zero entries refuse (B-merge-39). Flags are mutually exclusive and reject an extra positional / dirty tree. - A cherrypick/backout commit stays SINGLE-parent (REV is never a merge parent) and mints exactly one artifact 4.7.14 Q card (Q +rev / Q -rev), cleared one-shot; a plain merge keeps its two-token P card and empty Q. - The pending op is a new checkout special_op field (4th TLV, no default, threaded everywhere); undo serializes it (journal fmt v1->2) so undo unwinds a pending cherrypick/backout (B-merge-35). B-merge-1..29 e2e all still pass. 1675 unit + 362 e2e green; fmt clean. Follow-up: B-merge-41 (zit info Q-card render line) deferred — its pinned text was absent from the plan; the Q card is already readable via zit artifact. check-in: 20bf0a31c6 user: dmestas tags: cli | |
| 15:17 |
Add the annotate command: per-line provenance over the version chain
zit annotate PATH — which check-in introduced each line of one tracked file at the current checkout, and when. One canonical spelling (no blame/praise). The engine walks the file's full-ancestry version chain (repostate.ancestorsOf, the same 3.3 walk merge-base/bisect use; per-version content via Repository.fileAt, the one 8 effective-set query), diffs adjacent versions with the one house line-diff core (merge3.matchLines), and pushes each line's origin back to the OLDEST version carrying it. Across a merge, a line in ANY parent side is charged to that parent (primary first, repo-state 3.2), never to the merge; the merge is charged only for lines in no parent version. Output: 10-char hash, ISO date from event mtime, the line verbatim; a binary file refuses exit 1. Reviewed adversarially (merge attribution hand-traced correct). 1673 unit + 352 e2e green. check-in: f774a1a2a7 user: dmestas tags: cli | |
| 15:10 |
Track src/bisect.zig — omitted from the bisect commit 343d41b2f3
The bisect check-in landed every edited file but not the new bisect.zig itself (committed without a prior add), so that check-in would not build from a clean checkout. Add the file; no code change. check-in: b937bca55b user: dmestas tags: cli | |
| 15:09 |
feat(§6.2): same-path candidates in pack via manifest walk
Same-path predecessors sourced from check-in manifest walk, offered ahead of chronological window candidates (fossil's mlink analog per §6.2). fossil-scm-clone: zit=108143984B fossil=72245248B ratio=1.4969 [Step-4 packed store (measured this run), accounting EXACT (header64 + zlib + delta + config == 108,143,984): zlib 8369 frames / 82,268,624 B (76.1%, avg 9830 B); delta 59130 frames / 25,875,208 B (23.9%, avg 437 B); config 1 frame / 88 B; raw 0. Artifact-type split: checkin/manifest 20848 / 23,816,728 B (22.0%), other-artifact 6929 / 4,706,432 B (4.4%), file-blob 39722 / 79,620,672 B (73.6%). Header+padding overhead 3,710,123 B (3.4%).] | sqlite: zit=174352688B fossil=147709952B ratio=1.1804 [Step-4 packed store (measured this run), accounting EXACT (header64 + zlib + delta + config == 174,352,688): zlib 9248 frames / 113,074,896 B (64.9%, avg 12226 B); delta 121746 frames / 61,277,640 B (35.1%, avg 503 B); config 1 frame / 88 B; raw 0. Artifact-type split: checkin/manifest 36515 / 65,427,440 B (37.5%), other-artifact 4775 / 5,462,856 B (3.1%), file-blob 89704 / 103,462,240 B (59.3%). Header+padding overhead 7,211,093 B (4.1%).] vs baseline: fossil-scm-clone: zit=117528088B fossil=72245248B ratio=1.627 [raw=0B/0f; zlib=92073728B/9647f (78.3%); delta=25454120B/57852f (21.7%); config=176B/2f. Header+padding overhead 3711845B (3.2%). Artifact-type: checkin/manifest 26721968B/20848, other 4835264B/6929, file-blob 85970616B/39722. Accounting exact.] | sqlite: zit=207344544B fossil=147709952B ratio=1.404 [raw=0B/0f; zlib=140422768B/10847f (67.7%); delta=66921536B/120147f (32.3%); config=176B/2f. Header+padding overhead 7212808B (3.5%). Artifact-type: checkin/manifest 81006048B/36515, other 5531960B/4775, file-blob 120806296B/89704. Accounting exact.] check-in: 0375766879 user: dmestas tags: storage | |
| 14:40 |
Deepen log with an optional PATH: fold finfo into one verb
zit log CHECKIN [PATH]. With no PATH, behavior is byte-identical to today. With PATH, the ancestry walk emits only the check-ins that CHANGE that path: a check-in K changes PATH iff PATH's effective content (via Repository.fileAt, the one artifact 8 effective-set query — no CLI-side F-card re-derivation) differs from PATH's content in K's PRIMARY parent (first-listed P card, repo-state 3.2). Presence flips count (root add, delete, rename departure/arrival as delete+add, no old-name following); equal hashes are no change; a phantom parent reads as absent. walkLog gained an optional filter; the null path preserves the shipped DFS verbatim. The finfo/log split was historical accident, not design — one deep log replaces a shallow sibling, no new verb. 1661 unit + 343 e2e green; fmt clean. check-in: 6a360f33ff user: dmestas tags: cli | |
| 14:22 |
Add the bisect command: DAG binary search for a defect-introducing check-in
zit bisect bad NAME | good NAME | next | status | reset. Marking bad/good moves the checkout to the next probe automatically (fossil parity); next re-announces it; reset restores. The candidate set (full-ancestry ancestors of bad minus the good ancestor-sets minus skipped), the lower-median probe by ascending event-mtime with hash tiebreak, and the culprit are ONE general-purpose repostate query RepoState.bisectSpan, composed from collectAncestors / mergeBaseOf / isAncestorOf / mtimeOf / hashOf per the RESOLVED bisect-midpoint rule (repo-state 2/3.2/3.3/8.3) — the CLI never walks ancestry. Bisection state is checkout-local in .zit-bisect on the undo.zig state-file tier (atomic write + fsync, magic/CRC frame, corrupt -> exit 1), in the never-tracked/extras set; probe/culprit/reset moves go through the journal-armed update.applyCheckin behind requireCleanTree. Refusals (inverted marks, unrelated histories) exit 1. 1661 unit + 339 e2e green; fmt clean. check-in: 343d41b2f3 user: dmestas tags: cli | |
| 13:45 |
Add the artifact command: the plumbing window over the one parser
zit artifact HASH [--raw] — zit's cat-file/whatis. No flag: a whatis summary (hash, structural type per artifact 5.1/5.3, size, one line per A..W card in stored order, and the 6.2 Z self-checksum verdict). A blob reports type file, size, and binary-or-not. --raw emits the verbatim stored bytes. Resolves a 4-64 hex prefix over the LogStore (ambiguous -> exit 1), reads via the existing 7.2 blob path. Everything shown comes from the ONE existing artifact parser — refactored into frameNoZ/frame/assemble with a Z-tolerant parseWindow — never a CLI-side re-parse. 1649 unit + 335 e2e (13 new B-artifact) green; fmt clean. check-in: f477c3bf19 user: dmestas tags: cli | |
| 13:19 |
Add the amend command: mint display-metadata tags, never rewrite history
zit amend NAME [--comment TEXT] [--user NAME] [--date ISO] [--branch NAME] mints ONE control artifact whose T cards change how the target check-in displays — comment, user, date, and the repo-state 6.2 branch-move set — retargeted at the check-in hash, never '*'. History stays append-only: the original manifest is untouched and a new control artifact is crosslinked in (artifact 4.7.16 T / 4.7.5 D / 4.7.17 U / 4.7.19 Z; repo-state 4/5 crosslink, 5 tag propagation). Display is honored through a new general RepoState.activeTagValue / Repository.override consumed by timeline and info. Also removes amend from the git-reflex rejection table: teaching against fossil-semantics amend with the immutability lesson conflated it with git's history-rewriting amend — amend is in fact the proof of zit's immutability, not a violation. 1643 unit + 322 e2e (15 new B-amend) green; fmt clean. check-in: 6c6d1babc2 user: dmestas tags: cli | |
| 11:15 |
Resume the cli integration branch at trunk c2c837c5
The prior cli leaf was a closed integration point from before the recent slice; this reopens cli development at current trunk (timeline, mv, undo/redo, extras, clean, merge all present). The next verb slice — bisect, annotate, amend, artifact, addremove per the VCS verb survey — builds here and converges on cli, then one merge cli -> trunk. check-in: 9b5605ad79 user: dmestas tags: cli | |
| 08:21 |
feat(§6): pack re-deltification with chronological+wire-delta rebasing and monotonic-shrink guard
fossil-scm-clone: zit=110083984B fossil=72245248B ratio=1.5238 (packed store, exact accounting header64+zlib+delta+config==110083984: zlib 8377 frames / 84,763,872 B 77.0% avg 10118 B; delta 59122 frames / 25,319,960 B 23.0% avg 428 B; config 1 frame / 88 B; raw 0. Artifact-type: checkin/manifest 20848 / 23,816,728 B 21.6%, other 6929 / 4,706,432 B 4.3%, file-blob 39722 / 81,560,672 B 74.1%. As-cloned baseline was 117,528,088 B zlib 9647/92,073,728; delta 57852/25,454,120; ratio 1.6268.) sqlite: zit=175246544B fossil=147709952B ratio=1.1864 (packed store, exact accounting header64+zlib+delta+config==175246544: zlib 9260 frames / 113,048,584 B 64.5% avg 12208 B; delta 121734 frames / 62,197,808 B 35.5% avg 510 B; config 1 frame / 88 B; raw 0. Artifact-type: checkin/manifest 36515 / 65,427,440 B 37.3%, other 4775 / 5,462,856 B 3.1%, file-blob 89704 / 104,356,096 B 59.5%. As-cloned baseline was 207,344,544 B zlib 10847/140,422,768; delta 120147/66,921,536; ratio 1.4037.) vs baseline: fossil-scm-clone: zit=117528088B fossil=72245248B ratio=1.627 (raw=0B/0f; zlib=92073728B/9647f 78.3%; delta=25454120B/57852f 21.7%; config=176B/2f. Header+padding overhead 3711845B 3.2%. Artifact-type: checkin/manifest 26721968B/20848, other 4835264B/6929, file-blob 85970616B/39722. Accounting exact.) sqlite: zit=207344544B fossil=147709952B ratio=1.404 (raw=0B/0f; zlib=140422768B/10847f 67.7%; delta=66921536B/120147f 32.3%; config=176B/2f. Header+padding overhead 7212808B 3.5%. Artifact-type: checkin/manifest 81006048B/36515, other 5531960B/4775, file-blob 120806296B/89704. Accounting exact.) check-in: efaf2d3ae1 user: dmestas tags: storage | |
| 02:53 |
Merge merge-verb: the zit merge command (three-way merge + two-token P card)
Completes the checkout-and-history CLI slice — merge is the sixth and final verb (after timeline, mv, undo/redo, extras, clean). Line-based three-way merge into the working tree with fossil-style conflict markers, a mtime-independent up-to-date/ancestry gate, and a commit that mints the primary-first two-token P card and refuses while any marker remains. 1629 unit + 307 e2e green; dst 6x1000. check-in: c2c837c5e1 user: dmestas tags: trunk | |
| 02:51 |
Add the merge command: line-based three-way merge into the working tree
zit merge NAME computes a three-way merge of NAME (other) into the current checkout and records the merge parent; commit then mints the two-token P card. Built from the RFC oracle + the RESOLVED merge-base and conflict-model rules. - merge3.zig: a pure diff3 line engine + per-path classifier (no I/O, injected allocator, error{OutOfMemory}). Both-modified binary, add-vs-add, and modify-vs-delete are whole-file conflicts by construction, never laundered through the line merger (artifact §8.2; B-merge-21). Seeded-deterministic fuzz. - repostate.mergeBaseOf: the merge base over full ancestry (§3.3, merge parents included) — greatest event.mtime, ties by least artifact hash (§2/§8.3, the RESOLVED merge-base rule). repostate.isAncestorOf: a pure, mtime-independent full-ancestry membership test — the authoritative up-to-date gate, so a same-mtime ancestor can never slip the refusal. - merge verb: resolve NAME (§8, one resolver), refuse (exit 1, nothing mutated) a dirty tree / a pending merge / an up-to-date or unrelated NAME; else classify each §8.2 merge-walk path and materialize transactionally via update.applyMerge (journal + checkout written before the first byte moves). Conflicts write fossil-style markers, name each path on stderr, exit 1, and still record the pending merge; zit undo unwinds it (B-merge-13). - commit: with a merge pending, scan the committed set for the conflict sentinel and refuse until resolved (no override); otherwise emit the primary-first two-token P card and clear the merge one-shot (§4.7.13; B-merge-8/9). v1 non-goals held: no rename-aware, binary-content, cherrypick, or octopus merge. 1629 unit + 307 e2e green; dst 6x1000; fossil interop unaffected. closed check-in: 6bdb918f18 user: dmestas tags: merge-verb | |
| 01:22 | Create new branch named "merge-verb" check-in: 8404f79a7b user: dmestas tags: merge-verb | |
| 00:52 |
Merge next-verbs-2: extras and clean
Two untracked-file management verbs over one shared control-name/sweep module (artifact §8 never-tracked superset; the .zit control files and derived sidecars are the exclusion set): - extras: list files present in the working tree but not in the managed set and not control/derived files — the untracked surface, one path per line. - clean: remove those extras from the working tree, refusing to touch any tracked or control file; the sweep propagates allocation failure without leaking and never crashes on a hostile tree (fuzzed). Slice tail: merge remains unshipped (its plan/implement agent repeatedly lost its API connection mid-response this session). Full unit + e2e (290/290) green on the merged trunk. check-in: bbfa0715f0 user: dmestas tags: trunk | |
| 00:15 | Add the extras and clean commands over one shared untracked-file sweep: extras lists every checkout file zit is not tracking, and clean deletes exactly that set behind a --force gate, otherwise refusing non-interactively with a count because the deletion is irreversible and outside undo's shelter, per B-extras and B-clean-1/-5/-18. closed check-in: 7367551a07 user: dmestas tags: next-verbs-2 | |
|
2026-07-17
| ||
| 22:11 | Create new branch named "next-verbs-2" check-in: 9d7df286a6 user: dmestas tags: next-verbs-2 | |
| 22:08 |
Merge next-verbs: timeline, mv, and undo/redo
Three checkout-and-history CLI verbs, each built to its B-verb contract and RFC oracle, each gated green (unit + e2e) before commit: - timeline: list every check-in newest-first by event.mtime with an ascending full-hash tiebreak (repo-state §2, §8.3 item 7), merge check-ins marked M (§3.2), each on its branch with the configured main-branch fallback (§6.1/§6.4). - mv OLD NEW: rename a tracked file, moving it on disk and swapping the managed entry atomically; refuses (exit 1) an untracked OLD, an existing/tracked NEW, or a self-move; the rename records a one-shot NEW three-token on the next commit. - undo / redo: journal each sheltered mutation's pre-image (fsynced before the mutation) so update/revert are reversible; a corrupt or empty journal is a clean exit 1 with no partial restore. Slice partial: extras, clean, and merge were not completed this run. Full unit + e2e green on the merged trunk. check-in: 89ef496c3b user: dmestas tags: trunk | |
| 22:05 |
feat(§5): pickBase deep module + putArtifact lineage+chrono-window wiring with <75% accept threshold
fossil-scm-clone: zit=117528088B fossil=72245248B ratio=1.6268 [zlib 9647 frames / 92,073,728 B (78.3%, avg 9544 B); delta 57852 frames / 25,454,120 B (21.7%, avg 439 B); config 2 frames / 176 B; raw 0 frames. Header+padding overhead 3,711,845 B (3.2%). Exact accounting: 64 + zlib + delta + config == 117,528,088. Artifact types: checkin/manifest 20848 / 26,721,968 B (22.7%), other 6929 / 4,835,264 B (4.1%), file-blob 39722 / 85,970,616 B (73.1%).] | sqlite: zit=207344544B fossil=147709952B ratio=1.4037 [zlib 10847 frames / 140,422,768 B (67.7%, avg 12945 B); delta 120147 frames / 66,921,536 B (32.3%, avg 556 B); config 2 frames / 176 B; raw 0 frames. Header+padding overhead 7,212,808 B (3.5%). Exact accounting: 64 + zlib + delta + config == 207,344,544. Artifact types: checkin/manifest 36515 / 81,006,048 B (39.1%), other 4775 / 5,531,960 B (2.7%), file-blob 89704 / 120,806,296 B (58.3%).] vs baseline: fossil-scm-clone: zit=117528088B fossil=72245248B ratio=1.627 [raw=0B/0f; zlib=92073728B/9647f (78.3%); delta=25454120B/57852f (21.7%); config=176B/2f. Header+padding overhead 3711845B (3.2%). Artifact-type: checkin/manifest 26721968B/20848, other 4835264B/6929, file-blob 85970616B/39722. Accounting exact.] | sqlite: zit=207344544B fossil=147709952B ratio=1.404 [raw=0B/0f; zlib=140422768B/10847f (67.7%); delta=66921536B/120147f (32.3%); config=176B/2f. Header+padding overhead 7212808B (3.5%). Artifact-type: checkin/manifest 81006048B/36515, other 5531960B/4775, file-blob 120806296B/89704. Accounting exact.] check-in: d859c6a9a5 user: dmestas tags: storage | |
| 21:12 | Add the undo and redo commands, journaling each sheltered mutator's pre-image into .zit-undo before its first working-file byte moves so undo restores the last update or revert byte-for-byte and redo reapplies it, keeping one undo slot and one redo slot for fossil-parity single-level undo per contract B-undo-1..21 and fsyncing the journal ahead of the mutation for the B-commit-15 checkout-layer durability order. closed check-in: 14789e1409 user: dmestas tags: next-verbs | |
| 19:54 | Add the mv command, renaming a tracked file by moving the working copy from OLD to NEW, swapping the tracked-name entry, and recording the rename so the next commit emits NEW carrying OLD as its fourth old-name token per artifact section 4.7.6, persisting the managed set before the file moves to keep the tree recoverable under the B-commit-15 durability order. check-in: b9149b15e1 user: dmestas tags: next-verbs | |
| 19:07 |
bench(§4): step-1 at-rest baseline, fossil+sqlite corpora, CLONE/SYNC ingest
zit vs fossil at-rest bytes over the fossil-bench fossil-scm + sqlite corpora, imported via the clone/sync path (putDeltaContainer verbatim frames exercised): fossil-scm-clone: zit 117,528,088 B vs fossil 72,245,248 B -> 1.63x (67,499 artifacts) sqlite: zit 207,344,544 B vs fossil 147,709,952 B -> 1.40x (130,994 artifacts) Frame classes, artifact-type split, compression headroom, and serve CPU (203.9 / 138.4 MB/s) from 'zig build bench -- storage'. Chain-depth p50/p99 and a pure put us/artifact are NOT emitted by the current harness (plan §4.3 metrics-to-add); not synthesized (Rule 7). Comparison class: approximate (whole-file sizes, differing store shapes). Corpora unpinned; observed trunk hashes recorded in the JSON. check-in: 9aaf485847 user: dmestas tags: storage | |
| 16:58 | Add the timeline command, listing every check-in most recent first by event.mtime with an ascending full-hash tiebreak per repo-state section 2 and section 8.3 item 7, marking a check-in that joins two lines of development with M per section 3.2 and showing each on its own branch with the configured main-branch fallback per section 6.1 and section 6.4. check-in: b3a18a8ff0 user: dmestas tags: next-verbs | |
| 16:34 | Create new branch named "storage" check-in: 9478fe4fb7 user: dmestas tags: storage | |
| 15:58 | Create new branch named "next-verbs" check-in: cd5b6e94ed user: dmestas tags: next-verbs | |
| 15:20 |
Merge recover-inflight: root-URL sync serving + the log one-verb surface
Two independent slices recovered from an interrupted session, each gated green. - Sync now dispatches on method + Content-Type at any URL path (§4 L759-764), so a stock 'fossil clone http://host:port/' at the bare root interoperates; the /xfer-only requirement and its 404 are gone. - 'zit log' is reduced to the signed one-verb surface: one check-in ID, help only, no -n windowing; usage errors exit 2, a bad ID exits 1. Unit + e2e (209/209) green; fmt clean; dst 6x1000 ok. check-in: dbb2735bff user: dmestas tags: trunk | |
| 15:17 |
Reduce 'zit log' to the signed one-verb surface (drop -n windowing)
'zit log' carried a -n K windowed-log flag: an extra grammar mode (a state machine with a positional-only '--' separator and a -n value token in either order) and a bad_count offense, for a bound no other query verb exposes. Its sibling 'update' already documents the target shape — one full check-in ID, help only, no windowing. Collapse log onto that same one-positional grammar. parseLogArgs is now the plain scan parseUpdateArgs uses: -h/--help wins; -n, --all, and a bare '--' are all unknown options (log has no positional-only separator); a second positional is an extra argument. Drop LogArgs.count, the bad_count variant, parseLogCount, and walkLog's max_count / window early-exit — the ancestry walk is unconditionally unbounded again. Usage errors (missing check-in, unknown option, extra argument) now exit 2 via cli.usageError; a well-formed-but-non-canonical ID stays a validation failure at exit 1. Help and 'zit help log' print the trimmed single-verb text. Net -110 lines. Unit + e2e (209/209) green; fmt clean; dst 6x1000 ok. closed check-in: 0973cf7e9e user: dmestas tags: recover-inflight | |
| 15:15 |
Dispatch sync on method + Content-Type at any URL path, not just /xfer
A conformant Fossil client POSTs sync to the repository URL path unchanged and MUST NOT append /xfer (sync §4 L759-764): a bare 'fossil clone http://host:port/' POSTs to "/". The server previously required the /xfer path and answered 404 for anything else, so a root-URL clone from a stock client failed. Drop the path from the dispatch decision entirely. classifyXfer now keys only on method (POST) and a request-valid, outer-compressed Content-Type (application/x-fossil); the URL path is accepted but unused. Remove xfer_path, requestPath, and the not_found/404 disposition; Disposition collapses to ok / method_not_allowed / unsupported_media_type. A non-POST stays 405 rather than a home redirect this sync-only server can't honor (and a 302 to "/" would loop now that "/" is a valid endpoint). The over-real-HTTP e2e clone now targets the bare root "/" to prove a root-URL client interoperates. Unit + e2e green; fmt clean. check-in: 9636157a85 user: dmestas tags: recover-inflight | |
| 13:46 | Merge clone-w4: skip the receive-side inflate for full-form clone content, cutting clone peak RSS ~13% (651 -> 568 MB). Reconciles held()/content() across the pull .raw basis and push upload paths so a corrupt wire-backed container degrades gracefully instead of tripping an unreachable. 1538 tests pass; fossil interop smoke green. check-in: 19af0f2de7 user: dmestas tags: trunk | |
| 13:43 |
Skip the receive-side inflate for full-form clone content
The clone/sync receive path inflated every stored cfile container to prove the RFC1950 stream was intact (§3.9), then threw the result away: the store keeps the container verbatim and reconstructs lazily, and the persist commit hook runs the §7.7 integrity check on the reconstructed bytes. For the full form that inflate was pure waste. Move the decode into the delta branch (which genuinely needs the plaintext to reconstruct) so a full-form cfile is interned and retained without inflating. Retaining an unvalidated container means a held artifact is no longer guaranteed reconstructable: content() can be null for a corrupt wire-backed container, so held() no longer implies content(). Reconcile the consumers that assumed it did. The pull .raw delta basis and the push upload path both degrade gracefully (round abort / silent skip) instead of tripping an unreachable or assert on hostile input; the read/derivation path keeps the decompression-bomb cap, and the eager clone-end image treats a corrupt container as an error. Cuts clone peak RSS ~13% (651 -> 568 MB) on the fossil corpus by dropping the discarded plaintext allocation. Instruction count and wall are flat, since full-form cfiles are the minority of clone content. 1538 tests pass; fossil interop smoke green. closed check-in: d91a3cfe42 user: dmestas tags: clone-w4 | |
| 12:00 | Merge clone-w3: halve clone compute (-53% instructions, 2.3x faster wall) via a bounded persist-time plaintext memo, byte-identical store + trunk hash preserved. check-in: fbfef2b649 user: dmestas tags: trunk | |
| 11:58 | Halve the clone compute by memoising the persist-time plaintext: a bounded PlaintextCache reuses each frame's section 7.7-verify plaintext as the basis for the reverse-deltas built on it, killing the delta-chain reconstruction read-amplification (persist no longer re-inflates + re-applies a basis chain per dependent). libfossil clone: 138.2B -> 64.7B instructions (-53%), wall 13.4s -> 5.7s, RSS 550 -> 371 MB, trunk hash identical, store byte-identical (proven by test). The target's section 7.7 hash is still verified unconditionally after every reconstruction, so a stale basis is caught, never returned. closed check-in: 1bcbee0f86 user: dmestas tags: clone-w3 | |