| Document | draft-fossil-repo-state-00 |
| Section | 6.2 — Branch creation (symbolic-tag cancels; name-reuse check) |
| Type | Compatibility note (the draft is correct; Fossil's two creation commands differ from each other and, in corner cases, from the draft) |
| Status | Applied in draft-fossil-repo-state-01 (2026-10-12) |
| Reported | 2026-10-11 |
| Submitter | Dan Mestas |
| Found via | Cross-check of the illustrated reading and an older draft copy against Fossil 2.28 src/branch.c and src/checkin.c; confirmed by running Fossil 2.28 |
1. Summary
§6.2 is right as written. Two things Fossil does around it are worth recording, because an implementer comparing manifests or behavior against Fossil will meet them:
- The cancel cards differ by command.
fossil commit --branchcancels only the propagatingsym-*tags of the parent, which are exactly the ones the new root would inherit.fossil branch newcancels every activesym-*tag of the parent, singletons included, so its root manifest also carries cancel cards for tags the root would never have inherited. The active tags of the root are the same either way. - Fossil's reuse check is not §9's open-leaf test. It requires a
propagating branch tag and treats only a singleton
closedtag as closing. A branch whose leaf carries a propagatedclosedtag is listed as closed byfossil branch list, yetfossil branch newrefuses to reuse its name as open.fossil commit --branch --forceskips the check entirely.
2. Location
§6.2. Current text:
Branch creation MUST also cancel every pre-existing active sym-* tag on the branch-root check-in by emitting corresponding - (cancel) cards, because symbolic tags are otherwise inherited from a check-in's parent.
Branch creation MUST NOT succeed if the requested branch name already names an open branch. An implementation MUST check openness of the candidate name before emitting the branch-root manifest and MUST reject the operation if a branch of that name is already open.
3. The divergence
Verified against Fossil 2.28 by reading the source and by running it.
3.1 Cancel cards
Both commands read the tags of the parent check-in, since the root does not exist yet.
fossil branch new(src/branch.c:172-182) selectstagtype>0 AND tagname GLOB 'sym-*': singleton and propagating.fossil commit --branch(src/checkin.c:2099-2112) selectstagtype==2 AND tagname GLOB 'sym-*' AND tagname!='sym-'||<new name>: propagating only, and never the new branch's own name.
Run: trunk check-in c4 carries singleton sym-rel, sym-release, sym-relx
and inherits propagating sym-prop and sym-trunk.
$ fossil branch new b1 c4 $ fossil commit --branch b2 (from c4)
T *branch * b1 T *branch * b2
T *sym-b1 * T *sym-b2 *
T -sym-prop * T -sym-prop *
T -sym-rel * T -sym-trunk *
T -sym-release *
T -sym-relx *
T -sym-trunk *
The three extra cards in b1 cancel nothing the root had; they leave
cancel-type rows on the root and nothing else. §6.2's "every pre-existing
active sym-* tag on the branch-root check-in" is the inherited, propagating
set, which is what commit --branch emits. Both commands satisfy it.
3.2 Reuse check
branch_is_open() (src/branch.c:388-401), used by both commands
(src/branch.c:116, src/checkin.c:2735-2741):
SELECT rid FROM tagxref AS ox
WHERE tagid=<branch> AND tagtype=2 AND value=<name>
AND rid IN leaf
AND NOT EXISTS(SELECT 1 FROM tagxref AS ix
WHERE tagid=<closed> AND tagtype=1 AND ox.rid=ix.rid)
§9's open-leaf test, and Fossil's own branch listing
(src/branch.c:249-250, src/leaf.c:148-153), use tagtype>0 for both
tags. The two tests disagree in two corner cases:
Leaf of branch X carries |
§9 / fossil branch list |
branch_is_open |
|---|---|---|
propagated *closed |
closed | open: reuse refused |
singleton +branch X |
open | not open: reuse allowed |
Run (first row): fossil tag add --raw --propagate closed b1; fossil branch
list -c lists b1; fossil branch new b1 trunk fails with an open branch
named "b1" already exists. The control, a singleton closed on b2, lets
fossil branch new b2 trunk succeed. The second row is from the source only;
Fossil never writes a singleton branch tag itself.
In addition, commit --branch skips the check under --force and for the
name private (src/checkin.c:2735-2737), so Fossil can create a second open
branch of an existing name, which §6.2 forbids.
4. Impact
Small. The cancel-card difference is visible only in the root manifest's bytes
and in cancel rows on the root; resolution, branch membership and leaves are
unchanged. The reuse-check difference needs a propagated closed tag or a
singleton branch tag, neither of which Fossil's own branch commands write, or
the undocumented --force. Fossil's check disagreeing with its own branch
listing is Fossil's to fix; the draft should not copy it.
5. Correction
No normative change. Add an informative paragraph at the end of §6.2:
This paragraph is informative. Fossil computes the cancel cards from
the parent check-in. Its commit --branch command emits them only for
the parent's propagating sym-* tags other than the new branch's own,
which are the tags the root would inherit. Its branch new command
emits them for every active sym-* tag of the parent, singletons
included; the extra cards cancel nothing on the root. Fossil's
name-reuse check treats a branch as open only when a leaf carries a
propagating branch tag and no singleton closed tag, which differs
from the open-leaf test of Section 9 when a leaf carries a propagated
closed tag or a singleton branch tag, and commit --branch skips the
check when forced.
6. Rationale
- The draft states the useful rule. Cancelling the inherited set is what
keeps the parent's names off the new branch; the extra singleton cancels of
branch newhave no effect on derived state, so requiring them would add bytes without meaning. - One openness test. §6.2 relies on §9 for "open". Writing Fossil's
narrower check into §6.2 would give the draft two definitions of an open
branch, which is the inconsistency Fossil has between
branch newandbranch list. - Recording it saves a false alarm. An implementation that compares its
branch-root manifests with Fossil's will see the extra cancel cards from
branch newand should know they are not a conformance failure.
7. Verification
- Source: Fossil 2.28
src/branch.c:79-200(branch_new),:249-250(the listing's closed test),:388-401(branch_is_open);src/checkin.c:2099-2112(cancel cards forcommit --branch),:2735-2741(its reuse check);src/leaf.c:148-165(closed tests). - Run:
fossil new; four commits; singleton tagsrel,release,relxon c4;fossil tag add --propagate prop c2;fossil branch new b1 c4and, from c4,fossil commit --branch b2; compareTcards withfossil artifact. Thenfossil tag add --raw --propagate closed b1,fossil branch list -c,fossil branch new b1 trunk(refused);fossil tag add --raw closed b2,fossil branch new b2 trunk(succeeds).
Prepared against draft-fossil-repo-state-00. The RFC remains the oracle; the
reference implementation is used only to confirm the observable behavior.