zit

Timeline
Login

Timeline

50 ancestors of d72b6372abd8dff0

2026-08-29
16:57
status leads with Fossil's checkout block; changes drops it; bare info adds the count (zit#170)

Twelve-character names and the weekday in the times are the only departures from Fossil's shape. cli.shortHash and cli.humanTime are the primitives the rest of the terminal-speech work builds on. check-in: d72b6372ab user: dmestas tags: trunk

15:33
info with no NAME describes the current check-in; revert names only the files it changes

Both found by running one session through fossil and zit side by side. check-in: a0690841c1 user: dmestas tags: trunk

14:40
update: with no CHECKIN move to the branch's newest check-in, as Fossil does

A bare 'zit update' was a usage error; Fossil's means --latest. check-in: b618fc675d user: dmestas tags: trunk

12:44
logstore: putArtifactPreferring takes a known base before searching (zit#169)

The converter's hints are the source's own chains and each manifest's parent, so one delta usually settles it; the best-of-N window is the fallback. Conversion is now faster than the old convert-then-pack. check-in: 598d040de5 user: dmestas tags: trunk

12:04
import-fossil --submanifest: delta-encode the converted store as it is written (zit#169)

Blobs take the flat source's own delta base as the hint, manifests their parent, carried artifacts the recent window. A 28x full-form gap on a toy corpus became 1.3x of packed, with no second pass and no double-disk peak. check-in: 38b9743ff8 user: dmestas tags: trunk

10:18
release: prepare zit 0.3.0

Fossil parity on the everyday verbs, landings and strata, protected branches behind a trusted front, Fossil's user table, and the read paths that now scale with the change rather than the store. check-in: 1e2a7bbc0d user: dmestas tags: trunk, version-0.3.0

09:26
ui launches quarry's local mode (zit#165, quarry RFC 0010)

Runs quarry ui on the checkout root with the terminal inherited; the binary comes from ZIT_QUARRY or PATH, and an absent one is a three-line pointer, exit 1. check-in: 95553946e3 user: dmestas tags: trunk

09:07
diff --by, timeline -F html, and a reserved ui verb (zit#165)

--by writes a side-by-side page under $TMPDIR and opens it from a terminal; -F html renders the timeline as a table; ui names quarry's local mode and exits 1 until it exists. check-in: 87f4be2914 user: dmestas tags: trunk

08:46
settings editor and autosync pullonly, --changed/--value; grep -i -c -l -v --once; annotate -n -l (zit#164)

The two new settings ride the keyed config block; a configured editor makes commit editor-capable without a terminal. check-in: 7d9dcb2de1 user: dmestas tags: trunk

07:53
stash: snapshot, diff, goto, and apply --merge (zit#164)

--merge three-way merges a stashed change into a dirty file; conflicts get markers, are listed, and exit 5 with the entry kept. check-in: 21ce2c785d user: dmestas tags: trunk

07:35
merge: --integrate, --baseline, -n, and a bare merge onto the one other leaf (zit#164)

The checkout record carries the integrated leaf's hash as an optional sixth field; commit closes it with the same control artifact branch close mints. check-in: 2a522aab40 user: dmestas tags: trunk

07:01
diff: PATH operands, --numstat, --invert, --checkin, --branch (zit#164)

-Z is -w; -N is accepted (an added file's hunks already carry it all). check-in: 39c88046ab user: dmestas tags: trunk

06:35
mv takes many operands and directories; revert -r/-n, undo -n (zit#164)

The managed set records one pending rename per moved file. mv moves files into a directory or a tracked directory as a unit, --soft records without moving, -n previews. revert -r VERSION restores another check-in's content; revert/undo/redo -n name what they would touch. add -f takes ignored files; clean gains -n/-f and --emptydirs. check-in: ece16d74a9 user: dmestas tags: trunk

05:30
branch sub-verbs; clone opens its tree, open URL clones (zit#164)

branch new/close/reopen/hide/unhide/current/info. clone DIR defaults from the URL and is followed by open (--no-open keeps the bare store); open URL delegates to clone. check-in: 5d2f9a7e6e user: dmestas tags: trunk

03:36
timeline filters and formats, status classification, -R for the sync family

timeline gains -b/-c/-p/-u filters, -r and --offset, -F medium|full (a date header per day, *CURRENT* on the checkout's check-in, user and tags) and -v for changed files; the default line is unchanged. status gains --classify (Fossil's words, aligned), --added/--edited/--missing/--conflict/--extra/ --differ filters, -v's header block, CONFLICT for an edited file still carrying markers under a pending merge, and MERGED_WITH. pull, push, sync and remote accept -R, so a bare mirror needs no working tree. current/ ckout/prev/next take effect in these verbs through the same hint. Refs zit#163. check-in: 6c84f304f4 user: dmestas tags: trunk

02:33
Everyday verbs: update receipt, network narration, commit editor, checkout-relative names

update prints Fossil's receipt on stderr (per-file UPDATE/ADD/REMOVE/MERGE/ CONFLICT, the from/to block, the undo hint) and gains -n, --latest and -v. Every sync-family line names the peer, a no-op is always 'already in sync', clone prints the project code, an unreachable peer is named, and pull/push/ sync show per-round progress on a terminal. commit takes -M FILE, composes the comment in $VISUAL/$EDITOR/--editor over a template of the change set (kept in .zit-ci-comment when the commit then fails), and gains -n and --allow-empty; --no-warnings is accepted as Fossil's spelling of --no-checks — the content gate stays opt-in by design. The names current/ ckout, prev/previous and next resolve against the checkout. The muscle harness now enforces all of it. Closes zit#157, zit#158, zit#159, zit#160, zit#161. check-in: e5beabd7c0 user: dmestas tags: trunk

00:59
fossil-muscle-memory: a differential transcript through fossil and zit

zig build muscle runs one scripted session — 27 lines a Fossil user types most — through the real fossil and the real zit against local servers, and compares each step's exit-status class and whether the verb spoke at all. Deliberate divergences carry their reason, known gaps carry their issue; anything else fails the run. CI runs it beside smoke. Closes zit#162. check-in: 741f6d428b user: dmestas tags: trunk

00:59
Verb prefixes, Fossil aliases, and a suggester that never says 'rm'

Any unambiguous prefix of a command dispatches (zit up, zit tim); an ambiguous one names its candidates; an exact verb always wins, so stat stays the store census. Aliases: ci/checkin, co/checkout, changes, delete/forget/ remove, rename, blame/praise, new. A token of three letters or fewer is only suggested at edit distance 1, and an unknown verb prints two lines, never the command list. Closes zit#156. check-in: e114f7a174 user: dmestas tags: trunk

2026-08-28
21:29
import-fossil --submanifest: read and parse each baseline manifest once

repo.files re-read and re-parsed the B-card baseline for every delta check-in — 79% of the mint phase on pkgsrc. Repository.filesMemo takes a caller-owned BaselineMemo keyed by artifact name; consecutive deltas over one baseline borrow the parsed manifest and read nothing. On a 30k-file history of 300 deltas, source enumeration went from 10s to 0.3s and the conversion from 13.5s to 3.7s, every check-in hash identical. The memo holds a heap entry with a stable address; the optional-by-value form did not persist under ReleaseFast. Closes zit#167. check-in: c80728261e user: dmestas tags: trunk

16:04
submanifest convert, and import-fossil --submanifest straight from a fossil URL

The rebirth arm of import-fossil required a zit store, so the verb that says 'import a fossil' could not take one. It now accepts the fossil's sync URL like --compatible does, cloning a transient flat store beside DIR and removing it after; a local store stays accepted. The zit->zit conversion gets its proper home as 'zit submanifest convert SRC DEST', beside 'enable', which now says what it does not do: existing check-ins stay flat, and fossil peers receive nothing new from the next commit. Diagnostics carry the verb the operator typed. Closes zit#168. check-in: abd850dc4c user: dmestas tags: trunk

03:59
import-fossil --submanifest: drop the whole-history replay gate and --force

The gate replayed every commit through the from-scratch tree builder three times to predict what the conversion itself measures, and on pkgsrc that was 14 of 26 hours before a byte was written. The conversion writes a new project beside an untouched source, so the honest evidence is the converted store: the summary now reports its bytes against the flat source's. The partition parameter comes from --k or the tip sweep; a history no k partitions is refused, as 'submanifest enable' refuses it. 'zit submanifest analyze --replay-all' keeps the model for anyone who wants it first. Closes zit#129. check-in: 04d944c1dd user: dmestas tags: trunk

01:56
import-fossil --submanifest: build each check-in's tree from its parent's with the sub-manifest editor

Every check-in went through artifact.buildTree from scratch, so a wide tree cost its whole file set per commit against a few changed slices actually written. The converter now applies each check-in's change set over its primary parent's root with TreeEditor — the commit verb's builder — keeps a parent cache released as children consume it, interns the retained cards, runs the pair proof through the run-scoped flatten memo, and parses each source manifest once. The Git importer's LogTreeStore moves to a shared module. Output is unchanged: every check-in hash matches the from-scratch build on libfossil and on a 30k-file synthetic history. Refs zit#167. check-in: 5e0155427e user: dmestas tags: trunk

01:00
import-fossil --submanifest: strip SSH signatures, verify per pair in the mint loop, fold the census into pass 1, skip the replay under --k --force

The converter only stripped PGP armor, so an SSH-signed check-in re-minted with its envelope inside and failed its own verify silently. Verification now runs as each pair is minted, the census counts come from the first pass instead of a second corpus read, and --k narrows the gate's replay to one arm (skipped with --force). Each phase reports its wall time on stderr; the summary points at 'zit pack'. Closes zit#155. check-in: 75c8669553 user: dmestas tags: trunk

2026-08-27
20:02
merge settings-config-repo-flag-153: accept -R on a bare store for settings and config (#153) check-in: 26ed9cbc86 user: dmestas tags: trunk
19:00
settings, config: accept -R on a bare store

Their repository and global tiers, and config's user/shun areas, all live in the store, not the working tree -- fossil serves them under -R too. Move both out of the -R blanket refusal, resolve the override, and name a bad -R path with the #97 diagnostic rather than the discovery message that advises the flag already given.

Issue #153. closed check-in: b342686fd9 user: dmestas tags: settings-config-repo-flag-153

11:04
landing: update -- bring the target into the proposed branch on the store

The reverse of accept, through the same ephemeral-checkout merge, now one function that does not know which direction it runs. Always merges in a working area of its own, checkout or not, so a tree is never left half-merged. Up to date is exit 0 with nothing written; a conflict is refused with its paths; a settled record is refused; a stacked landing is not, since refreshing a rung is what the rungs above it need. The refresh is a reply in the record's thread. Issue #152. check-in: c4b949ebbf user: dmestas tags: trunk

2026-08-26
19:43
merge serve-protected-branches: per-request branch protection behind a trusted front (#151) check-in: d46cb9a855 user: dmestas tags: trunk
16:57
serve: refuse a check-in's own singleton claim on a protected name; warm the image only after a push

A self-targeted +sym-B is not how a branch is joined, but nothing legitimate mints one on a protected name. maintain now derives the fast-read image only when the log grew, so a pull-heavy fronted server does not pay an image load per connection. Pins that a merge with a protected secondary parent stays on its primary's branch. Issue #151. closed check-in: 13760d0c92 user: dmestas tags: serve-protected-branches

16:14
serve: refuse membership changes to protected branches, per request, from a trusted front

A front that authenticates every push knows who is pushing; the store knows which branch a pushed check-in resolves to. The front sends only the rule -- X-Zit-Protect, a glob list -- and zit serve --fronted refuses the whole round when a check-in would join one of those branches or a tag would move a held check-in in or out of one. Same error card as a content-policy refusal, so a stock fossil client reads it.

Without --fronted the header is ignored and the server is unchanged. Nothing new on the wire or in the repository. Resolution is crosslink's own, through the fast-read image, which maintain keeps warm between connections. A parent not yet held is admitted: it is judged on arrival. Resolver faults fail closed. Issue #151. check-in: 63ec7d6dc8 user: dmestas tags: serve-protected-branches

14:35
merge landing-reply-to-149: --reply-to aims a landing comment's I-card (#149) check-in: 2892f40e8b user: dmestas tags: trunk
14:05
landing: --reply-to aims a comment's I-card at a post, not just the root

comment collapsed the G and I cards onto the record root, so a landing thread could not branch. Split mintForum's root and reply-to; landing comment takes --reply-to POST, resolving the thread root with the same helper forum uses and refusing a target outside the record's own thread. closed check-in: fed51b20cb user: dmestas tags: landing-reply-to-149

2026-08-25
21:19
merge rfc-vector-guard: vectors inert on disk check-in: ca7453995e user: dmestas tags: trunk
21:09
rfc vectors: keep them inert on disk

An unguarded vector parses as a real artifact, so Fossil crosslinks it into every clone -- two check-ins, a technote, twelve sub-manifest slices and a phantom pair that sync chases forever. Each vector now carries a leading # block; rfc_vectors.zig strips it at comptime so callers still see the exact RFC bytes.

Background and the cleanup that follows: see wiki page 'rfc-vector-ledger'. closed check-in: 32ef56aaf4 user: dmestas tags: rfc-vector-guard

18:50
merge fix-148-prefix-lookup: byte-aligned name lookup (#148) check-in: 3f8aa36a1d user: dmestas tags: trunk
18:30
logstore: answer a byte-aligned name prefix from the sorted directory

A complete SHA-1 name is shorter than max_hash_len, so it missed the full-width fast path and fell to the O(store) hex scan -- once per file under `ls --size` and `ls --long`. On tcl's tip the 545 SHA-1 named files cost ~54ms each against ~0.2ms for the 1,653 SHA3 ones.

The directory is ordered by raw digest, so a prefix's matches form one contiguous run. A binary search for the run plus a walk of it proves uniqueness rather than assuming it, which is what the SHA-1 case was surrendered to the scan for. Odd-length prefixes end mid-byte and keep the scan.

ls --size on tcl: 1.98s -> 0.03s. Issue #148. closed check-in: 4829f043b1 user: dmestas tags: fix-148-prefix-lookup

2026-08-24
21:41
merge strata: stacked landings (#144) check-in: 2e060aa7c0 user: dmestas tags: trunk
20:23
landing: pin the stack-depth bound the indent trusts

The cycle panic was in the indent computation, and rewriting it made the panic go away without my being able to explain the arithmetic -- 32*2 into a 64-byte buffer should not overflow. This asserts the bound directly, so the renderer's assumption is checked rather than believed. closed check-in: 5a35874240 user: dmestas tags: strata

20:10
landing: cycles, unresolvable targets, and already-contained branches

Three holes found by walking the scenario space rather than the happy path.

A retarget could make a cycle. Every segment in one then refuses forever, each waiting on the next, and 'ls' panicked outright computing an indent for a chain with no bottom. Cycles are now refused, direct and indirect.

A retarget accepted a branch name that does not resolve, minting a permanent record aimed at nothing -- the check 'open' has always made before minting one.

A branch already contained in its target reported 'did not merge cleanly; resolve and commit'. There was nothing to resolve: 'merge' had said 'already up to date'. The record could then only be closed by rejecting it, recording the opposite of what happened. It now settles as accepted with no merge, which is what is true. Reachable from a stack by landing the top segment first -- it carries the ones below it in. check-in: b450374bce user: dmestas tags: strata

19:17
landing: refuse a landing whose base was rejected or already landed

The ordering guard only looked for an OPEN base, so a rejected one slipped through and the accept SUCCEEDED -- 'accepted seg2 -> seg1', filing the work on a branch nobody would land. Nothing failed and nothing warned; the merge is fine, which is why only a rule catches it. Same hole for a base that already landed, reachable when the auto-retarget after that accept did not run.

All three cases now refuse and name the retarget that fixes them.

Documented the half of this that surprises: rejecting a landing rejects the proposal, not the code beneath a segment stacked on it. That branch descends from the rejected one and still carries its check-ins, so landing it on trunk lands them too. check-in: f91105346b user: dmestas tags: strata

18:52
landing: close the landed leaf without propagating

The propagating form marked every segment ABOVE a landed one closed too: in a stack those check-ins descend from the landed tip, so propagation followed the DAG straight into work that was still open. The landing records stayed open and correct; only 'branch' and 'leaves' lied, which is exactly where you look for what still needs doing.

Fossil's own propagating close is safe because it closes a check-in with no descendants. A landed segment is a leaf by the same-branch rule while still having children on the branch above it. check-in: d97b39b272 user: dmestas tags: strata

18:46
merge: hardware crc32 and index v3 sorted directory (#143, #145) check-in: 2a676a5df1 user: dmestas tags: trunk
18:33
logstore: index v3 carries a sorted directory, so opening builds no hash map

Adopting an index inserted every entry into a StringHashMap before answering anything: 255,167 keys on a 302MB store, to serve the one or two lookups a tree listing makes. Measured at 5.5ms of a 24ms open by building the map twice and taking the delta.

v3 appends entry_count little-endian u32 rids ordered by digest, and lookup binary-searches it -- ~18 comparisons against a quarter-million insertions. Opening tcl drops 24.0ms to 20.0ms; with the hardware crc32 change before it, 38.5ms to 20.0ms. Output byte-identical.

The directory carries RIDS and the entry records stay in scan order. Sorting the entries themselves would renumber every rid, and ancestry, phantom snapshots and the sync walk all read that numbering.

lookup consults the map first, then the directory: entries appended after adoption -- a tail resume past the index boundary, and every write on a scan-path store -- are only in the map. Every held-check that was reading index.get directly now goes through lookup, because intern runs during a tail resume and a miss there mints a duplicate entry for a digest already held.

A v2 sidecar is refused and rewritten rather than adopted, as v1 was: adopting one would leave lookup with neither a map nor a directory and answer null for held artifacts. Pinned by a test that downgrades a v3 blob and re-checksums it.

The tests pin what equality cannot: that the map is EMPTY after adoption and every digest still resolves, that a digest which is not held returns null rather than a neighbour -- the failure a binary search has and a map does not -- and that digests written out of sort order still resolve, so a directory that merely mirrored insertion order would fail.

Closes #145 closed check-in: 0d16bc966d user: dmestas tags: index-v3-145

18:16
landing: close the branch an accepted landing lands

Every landed branch stayed an open leaf, which was survivable one landing at a time and is not with stacks: a six-deep stack left seven leaves, and 'leaves' is how you find work that still needs doing.

accept now writes fossil's propagating 'closed' tag on the branch it merged. reject does not -- a rejected branch is one the author may revise and propose again, and closing it would decide that for them.

Best-effort and last, like the retargets: a leaf that stayed open is untidy, never incorrect, and the diagnostic names the command that fixes it. check-in: c9b0147381 user: dmestas tags: strata

17:53
crc32: fold with ARMv8's crc32 instruction where the target has it

Checksumming was half the cost of opening a repository: 18.8ms of a 37ms `ls` on a 302MB store, across the two sidecars (26.3MB derived-state image at 13.5ms, 9.7MB log index at 5.3ms). Both measured by doubling the work and taking the delta, so the number is the checksum and not its neighbours.

ARMv8's crc32b/crc32x implement CRC-32/ISO-HDLC -- the same generator this file is already defined in terms of, in the same reflected convention -- so this is an instruction-count change and never a value change, which is what the module doc says every edit here must be. tcl opens in 24.5ms against 38.5ms, output byte-identical.

aarch64 only, deliberately. x86_64's SSE4.2 crc32 instruction is CRC-32C/Castagnoli, a DIFFERENT generator; taking it because the mnemonic matches would orphan every store already written. x86_64 keeps the table path, which stays load-bearing rather than becoming a fallback nobody runs.

The new test recomputes CRC-32 as a plain bit-at-a-time recurrence, independent of update and its tables, and holds both paths to it at every length 0..1024 -- all eight residues mod 8, including the lengths below the fold width. The two paths differ only in where the fold stops and the tail begins, so a divergence would appear at particular residues and nowhere else, which is the shape a spot check misses. Chunked updates are pinned too: the store checksums payloads in 4 KiB pieces, so a path correct only for single-shot input would corrupt exactly the largest artifacts.

Refs #143 closed check-in: b82aacc805 user: dmestas tags: crc-hardware-143

17:40
landing: stacked landings (strata)

A landing may target another branch that has its own open landing; that pair is a stack, read from the targets rather than recorded anywhere. No new card, no dependency field -- the edge is already implied.

Stacks land bottom-up. accept refuses a landing whose target has its own open landing, and accepting one retargets whatever was stacked on it onto the branch it just landed on. That is the whole restack: a metadata write per child, because a segment's branch already descends from the one below it, so after that lands the merge sees only the segment's own change.

Adds 'landing retarget', a target reply resolved by the same latest-wins rule as a state transition. ls indents a stack by depth; show names what a segment sits on, what sits on it, and when its base was rejected.

Verified six deep on a shared file, which is the depth the design claim was previously only reasoned about. check-in: 3767f1eafc user: dmestas tags: strata

16:50
merge index-borrow-143: adopted index entries slice the blob (#143) check-in: 3c3f936632 user: dmestas tags: trunk
16:41
logstore: adopted index entries slice the blob instead of copying it

Adopting a scan index duplicated every entry hash: 255,167 allocations and copies on a 348MB store to answer, typically, one lookup. The hashes are fixed-length slices of a buffer that was being freed at the end of the open, so the store now holds that buffer for its lifetime and the entries point into it. Opening tcl drops 44.0ms -> 37.5ms; smaller stores scale down with the index.

openReadOnlyIndexed takes the blob UNCONDITIONALLY -- on success the store frees it in deinit, on every refusal it frees there. The first cut transferred only on success, both call sites looked right, and the second one double-freed inside commit's autosync. No type and no unit test caught it; only e2e did. A conditional transfer makes every call site restate the rule.

Ownership is a prefix, not a per-entry flag: adopted entries come first and a tail resume appends owned ones after them, so borrowed_entries is the split point deinit and the batch rollback both read.

The unit test pins that the hashes lie INSIDE the held blob, not merely that they compare equal -- equal bytes prove nothing about where they live, and a future dupe would restore the cost with every assertion still green.

This is one part of #143. The CRC that issue names as the largest fixed cost measures 5.2ms of the 44ms (1,860 MB/s over the 9.7MB index), so it is not where the time goes and its integrity check should stay.

Refs #143 closed check-in: 1234842809 user: dmestas tags: index-borrow-143

16:08
merge merge-dirty-tree: merge into a dirty working tree (#135) check-in: 944ee007fe user: dmestas tags: trunk
15:17
merge: merge into a dirty working tree instead of refusing it

The current side becomes the working tree, so a divergent path compares what the operator actually has rather than what the check-in says is on disk -- which is the disagreement B-merge-14 existed to avoid. A clean tree scans to no divergences and every side reads from the store exactly as before.

Rollback needs no new machinery: captureMergeImages already reads the worktree, so an abandoned merge restores the uncommitted bytes.

Also adds the fault-injection coverage the carried update path was missing: a carried update that fails mid-apply must restore the WORKING bytes, not the check-in's. Verified by mutation -- breaking the baseline overlay fails both tests. closed check-in: 0627425be0 user: dmestas tags: merge-dirty-tree

14:47
merge readpath-141: diff and artifact read through the image path (#141) check-in: 9bcc83b530 user: dmestas tags: trunk
14:36
diff and artifact: read through the image path, not a whole-store derive

Both opened `.full`, so each invocation re-derived the entire repository view before reading anything. The cost tracked the STORE, not the work: a one-line diff on a 302MB store took 0.33s, and cost the same whether the diff was 13 lines or 4126. diff read two file lists -- exactly what ls reads at 0.04s -- and artifact never touched the derived view at all, resolving through logStore alone.

diff inherited `.full` by omission: it called Repository.open directly instead of going through main.openRepo, which is where every other read verb chooses a mode. Nothing failed, so nothing caught it.

fossil 112MB 0.06s -> 0.01s tk 240MB 0.14s -> 0.03s sqlite 236MB 0.25s -> 0.03s tcl 302MB 0.33s -> 0.04s

Output byte-identical on all four.

readpath_test pins which verbs take which path, and pins the comment justifying each deliberate `.full` -- timeline reads the merge mark off adjacency, annotate walks ancestry. An unexplained `.full` is indistinguishable from this bug. grep is named there as an open question rather than pinned, since it also wires --no-cache, which has to move with the mode.

The OpenOptions comment claimed ls, cat, info, branch and tag were `.full`. All five have been fast_read since #98; reading it was how this stayed hidden.

Fixes #141 closed check-in: e845821a65 user: dmestas tags: readpath-141