Skip to content
zit Erratum — draft-fossil-repo-state-00 §7.3
Login

Erratum — draft-fossil-repo-state-00 §7.3

Document draft-fossil-repo-state-00
Section 7.3 — Maintenance algorithm (full rebuild; deferred scheduling)
Type Technical (two divergences from the reference implementation; the draft's leaf definition is unaffected)
Status Proposed — operator decision pending
Reported 2026-10-11
Submitter Dan Mestas
Found via go-libfossil's conformance suite against Fossil 2.28; confirmed by reading Fossil's source and running it

1. Summary

§7.2's leaf test and §7.3's full rebuild agree: a check-in with no same-branch child is a leaf, and §7.3 says outright that a check-in with no parent "is a leaf when it has no same-branch child, and MUST be included." Fossil keeps its leaf table by a narrower rule, in two places:

  1. Its full recompute excludes every check-in with no parent. The query draws its candidates from the parent-link table's child column, so a root is never a candidate. On a new repository, fossil leaves --recompute leaves no leaves at all.
  2. Its incremental maintenance runs only on branch-tag events. A leaf recheck is scheduled when a branch tag is set on a check-in or propagated to it, never when a parent-link is created. A check-in with no parent and no branch tag receives no such event, so it never becomes a leaf.

Every check-in Fossil itself writes carries a branch tag (its own, or one propagated from its parent), so for Fossil-authored history the two rules agree, except under --recompute. They differ for a parentless check-in with no branch tag, such as the §A.1.1 example of the artifact-format draft: under the draft it is a leaf, and in Fossil it is not.

2. Location

§7.3, two bullets. Current text:

Full rebuild: … The candidate set is every check-in, not only those that appear as a child of some parent-link. A check-in with no parent at all (for example the sole check-in of a new repository, or any root check-in) is a leaf when it has no same-branch child, and MUST be included.

Deferred scheduling: implementations MUST schedule an incremental leaf recheck for the affected check-in and for each of its direct parents when a parent-link is created or a branch tag is applied or propagated.

3. The defects

Verified against Fossil 2.28 by reading the source and by running it.

3.1 The full recompute has no roots

leaf_rebuild() (src/leaf.c:85):

DELETE FROM leaf;
INSERT OR IGNORE INTO leaf
  SELECT cid FROM plink
  EXCEPT
  SELECT pid FROM plink WHERE <parent and child on the same branch>

Its only caller is fossil leaves --recompute (src/descendants.c). Plain fossil rebuild does not call it. On a repository holding only the initial check-in of fossil new:

$ fossil leaves                 # (1) [eeb2389b69] initial empty check-in
$ fossil leaves --recompute     # nothing; the leaf table is empty

The draft's rule is the coherent one: the incremental path keeps that same check-in as a leaf, so Fossil's two paths disagree with each other. This half is a Fossil defect, and the draft needs no change for it beyond recording it.

3.2 Scheduling is driven by branch tags alone

leaf_eventually_check() (src/leaf.c:172), which queues a check-in and its parents for a recheck, has exactly two callers, both in src/tag.c: setting a branch tag on a check-in (:195) and propagating one to a child (:101). Crosslinking a check-in and its parent-links schedules nothing. A parentless check-in with no branch tag therefore never reaches leaf_check():

Check-in in one repository (Fossil 2.28, after fossil rebuild) parents branch tag leaf
eeb2389b69, the initial check-in of fossil new none trunk yes
a28c83647d, the §A.1.1 artifact, stored with test-content-put none none no

4. Impact

Observable, rare. zit leaves follows the draft (src/repostate.zig: the leaf candidates are every check-in) and lists a parentless check-in with no branch tag. fossil leaves on the same repository does not. Fossil never writes such a check-in, so it reaches a repository only by import or by synthesis.

fossil leaves --recompute is more serious for Fossil users, since it drops the sole leaf of a new repository and every root of a multi-root one, but that is Fossil's to fix.

5. Correction

The leaf definition (§7.1, §7.2) and the full-rebuild bullet stay as they are. Two options for the rest, which are an operator decision.

Option A (recommended): keep the draft's rule, and record Fossil's divergence. The draft has no compatibility appendix, so add an informative paragraph at the end of §7.3:

   This paragraph is informative.  Fossil's full leaf recompute (fossil
   leaves --recompute) draws its candidates from the child column of the
   parent-link table, so it omits every check-in with no parent, which
   this section requires.  Fossil's incremental maintenance schedules a
   recheck only when a branch tag is set on a check-in or propagated to
   it, so a check-in with no parent and no branch tag never enters its
   leaf table.  Every check-in Fossil writes carries a branch tag, so the
   two agree on Fossil-authored history.

and make the scheduling bullet say what the leaf test needs, so that every check-in is rechecked at least once:

-      parent-link is created or a branch tag is applied or propagated.
+      check-in is crosslinked, when a parent-link is created, or when a
+      branch tag is applied or propagated.

Option B: describe Fossil. Narrow §7.3 so that a check-in is a leaf candidate only when it is the child of a parent-link or carries a branch tag, and drop "MUST be included" for parentless check-ins. This matches Fossil's incremental path, but writes its --recompute defect into the draft, and contradicts §7.2's test for an untagged root.

6. Rationale

7. Verification


Prepared against draft-fossil-repo-state-00. The RFC remains the oracle; the reference implementation is used only to confirm the observable behavior.