Skip to content
zit Artifact [68072b67d8]
Login

Artifact [68072b67d8]

Artifact 68072b67d8daad96767d997062ad4d88e1b2bd80e33d2cb5e6150d59133427b7:


zit 0.8.0

zit 0.8.0 carries Git submodules. import-git keeps each commit's submodule pins in a tracked .zit-modules file, and export-git writes them back as gitlinks, so a Git history with submodules goes through zit and back with the same trees. zit modules lists, syncs and pins the modules a checkout names, and import-git --with-submodules imports each submodule beside the parent so its pins name zit check-ins. A squash- merged pull request is recorded as Fossil's cherry-pick of the branch it took, so the branch no longer ends unjoined on the timeline.

This release is from trunk. Stores are unchanged from 0.7.5.

Submodule pins survive a Git round trip

Git records a submodule as a gitlink: a tree entry of mode 160000 naming a commit in another repository. Fossil's artifact format has no such entry, so fossil import --git drops the pin, and so did zit, silently. Exported back, the history had a .gitmodules and no gitlinks, which Git treats as a broken superproject.

Now each commit's pins go into a tracked file at the root, .zit-modules, one module per line, sorted by path:

lib git https://example.test/lib.git 1111111111111111111111111111111111111111

The four fields are path (with the F-card filename escapes, \s for a space), kind (git or zit), url (from that commit's .gitmodules, or - when it names none) and the full pin. .gitmodules stays a tracked file. Fossil and older zits see .zit-modules as a plain file; the artifact format is untouched. The import writes the file only where a commit's tree holds a gitlink, and removes it with the last one.

export-git turns the ledger back into gitlinks instead of copying it: each git line becomes a mode-160000 entry naming its pin. Measured on the test histories, the exported commits' tree IDs equal the source's, gitlink rows included.

The import says how many submodule paths it pinned, on stderr and as submodules.paths in the -F json result. A source repository's own .zit-modules at the root is left out, as other names zit keeps are.

The design is recorded in the technote "Submodule pins in .zit-modules".

zit modules

A zit module is a nested zit checkout at its path.

Nothing commits or pushes a module: it is a repository of its own. A git line pins a Git commit zit cannot check out; sync names it and leaves it alone.

import-git --with-submodules

With --with-submodules, every submodule any .gitmodules in the source's history names is first mirrored with Git and imported into a sibling of DEST, <name>.zit, or added to with --into when that sibling is an import already. A pin on a commit the sibling holds is then written as a zit line naming that check-in and ../<name>.zit, which zit modules sync can check out. export-git maps a zit line back to its Git commit through the sibling's import map, and refuses a pin it cannot map, naming the path and the sibling.

A module that cannot be fetched or imported is reported and its pins stay git lines; the parent import goes on without it.

A squash merge is a cherry-pick of its pull request

A pull request merged by squashing leaves one new commit on the default branch with a single parent, so its branch's commits never become its ancestors and the branch ends in mid-air on a mirror's timeline. Fossil already has the relation a squash is: the cherry-pick, drawn as a dashed line from what was taken to the check-in that took it.

When the source holds refs/pull/N/head (GitHub keeps it for every pull request, after its branch is deleted too) and a single-parent commit on the default branch's first-parent chain has a subject ending (#N), the import gives that check-in a Q +HASH card naming the pull request's head. A head that is already the commit's ancestor, as after a merge or a fast-forward, gets none, and neither does a commit whose head the import has not seen. A source without pull refs imports exactly as before. The import says how many squashes it linked.

A mirror needs refs/pull/*/head in the source clone for this, for example with a fetch refspec of +refs/pull/*/head:refs/pull/*/head.

The rule is recorded in the technote "A squash merge is a cherry-pick".

The tree diff reads only what changed

The diff between two sub-manifest trees, behind the timeline's file lists and everything that asks what a check-in changed, paired sub-manifests by name. When a partition boundary moved, or the two trees had different heights at the same name, it read whole subtrees and filtered the extra rows afterwards: the cost tracked the subtree, not the change. It now walks the two trees side by side in name order, skips any pair of sub-manifests with the same hash unread, and opens only the taller of two that tie, so it reads only the sub-manifests on changed paths and reports exactly the files that differ. The technique is libfossil's (fsl__deck_tree_diff).

Known limits

zit modules sync checks out zit modules only. A git line needs the parent re-imported with --with-submodules, or Git.

A store's check-ins are never rewritten. So:

Compatibility

Stores and the wire are unchanged from 0.7.5. The submodule ledger is an ordinary tracked file, so every Fossil peer and every earlier zit stores, syncs and checks it out as one.

An import of a history with submodules now writes .zit-modules into the commits that have gitlinks, and an import of a history with paired squash merges writes Q cards, so those check-ins' names differ from what 0.7.5 gives the same history. A history with neither imports byte-for-byte as 0.7.5 imports it.

Qualification

The release manifest records native qualification against the exact tagged source archive on macOS ARM64, Linux x86-64 and Linux ARM64, using the pinned Zig 0.17.0 toolchain. Publication is gated on unit and end-to-end tests, Git differentials, deterministic simulation, seeded fuzzing, and Fossil interoperability checks on every distributed target.

For the basis of the conformance claim, see the 0.1.0 release notes.