zit 0.3.28 adds the counterpart to config import, so a configuration area
can be backed up before it is replaced, and lets merge --probe answer a
whole stack of branches the way a landing will meet them.
No artifact, tag namespace, sync message, or on-disk repository format changed.
zit config export AREA [FILE]
config import replaces a configuration area from a file and config merge
folds one in, but nothing wrote the current area out. The round trip is the
point of the pair: back the table up, replace it, restore it if needed.
zit config export user backup.txt
zit config import other.txt
zit config import backup.txt # the table is back as it was
The output is fossil's configuration export format: a # banner, then one
config /AREA SIZE card per row, byte-identical to what the sync protocol
carries for that area — the same cards import and merge read and the
same cards a fossil repository writes. FILE absent or - is stdout. The
file carries no timestamp, so two exports of an unchanged table are the same
bytes and a backup can be compared with cmp. The user area includes
password hashes; the help says to treat the file as you would the
repository.
import and merge now also read /shun cards; before, they counted them
as an area zit does not model and skipped them. Shun rows are added in
both modes. import still replaces the user table, but replacing the shun
table would un-shun whatever the file does not name, and a shun is never
undone here — not by zit shun, and so not as a side effect of restoring a
backup. config pull already held that line. The round trip is therefore
exact for user and add-only for shun.
The wire and a file now share one /shun payload parser, so a file cannot
admit a row the wire would refuse.
zit merge --probe answers a stack
merge --probe NAME --into TARGET answers whether one branch merges cleanly
into one target. A stacked landing asks something else: whether rung 3
merges into the target once rungs 1 and 2 are on it. Probing each rung
against the one below cannot answer that, because it knows nothing of what
the target changed in the meantime. It can call a rung clean that stops the
landing.
Given several NAMEs, --probe now answers the landing's question:
$ zit merge --probe --into trunk r1 r2 r3 r4
merges r1 clean
merges r2 clean
merges r3 conflict (1)
conflict f.txt
merges r4 not-reached
Each rung is merged, in memory, onto the target with the rungs before it
already landed. A rung after one that stopped is not-reached: a merge that
did not complete leaves nothing to continue from. A rung already inside the
landed tree is up-to-date and the chain continues. Still read-only, still
exit 0 in every case. One NAME prints exactly what it always has, and a
real merge still takes one NAME.
Two things make the chain more than a loop over the existing probe:
- The landed tree is held as content, not names. The single probe compares file hashes and only reads blobs to decide a conflict. A cleanly merged file has no name in the store until someone commits it, so the next rung's side is a map of stored blobs or merged bytes.
- The merge base moves with the landing. Once rung 1 has landed, the target's tip has it as an ancestor, so rung 2's base is the nearest common ancestor with anything landed, not the target's old fork point. Merging the landed tree against the old fork point would see rung 1's edit on one side and rung 2's rewrite of it on the other, and report a stacked rung as conflicting with its own parent. The test for this was checked by breaking the base on purpose: it then reports exactly that false conflict.
Compatibility
- No on-disk repository or wire format changed; Fossil RFC revision 00 remains the target.
zit config import|mergenow apply/shuncards (add-only) instead of ignoring them. The summary prints one line per area touched.zit merge --probewith more than one NAME was previously refused as an extra argument; it now probes the chain.
Qualification
zig fmt --check, zig build test, e2e, smoke, git-import-e2e,
git-export-e2e, and dst, plus the three-runner CI battery.