Help us shape the future of Go! Take the 2026 Go Developer Survey

leaf

package
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package leaf maintains the leaf table, in one place so every writer of a branch tag -- crosslinking, a local commit, a tag control artifact -- and the explicit rebuild agree on what a leaf is and on when one is rechecked.

It is a package of its own, below both internal/tag and internal/manifest, because the tag package is where branch rows are written (and so where a recheck must be scheduled), while manifest owns the crosslink batches that apply the schedule. Neither can import the other in that direction.

Canonical fossil's rule (src/leaf.c, leaf_check/is_a_leaf/leaf_rebuild): a check-in stops being a leaf only once a child of it carries the *same* branch name, where a check-in with no active branch tag reads as the default branch (here, the main-branch setting, see mainBranchSQL):

SELECT 1 FROM plink
 WHERE pid=:rid
   AND coalesce((SELECT value FROM tagxref
                  WHERE tagid=TAG_BRANCH AND rid=plink.pid
                    AND tagtype>0),'trunk')
    == coalesce((SELECT value FROM tagxref
                  WHERE tagid=TAG_BRANCH AND rid=plink.cid
                    AND tagtype>0),'trunk')

Only an active branch row (tagtype > 0) names a branch. fossil 2.28's is_a_leaf, leaf_check and leaf_rebuild do not filter so: a check-in whose branch tag was cancelled with a value keeps that value there. draft-fossil-repo-state-00 Section 6.4 requires the filter on every path that decides branch membership, so both paths here apply it. Audit, which must also accept fossil's tables, reads both ways (fossilSameBranchSQL).

Two consequences are easy to get wrong and are what issue #189 was:

  • The merge parent of a check-in on another branch stays a leaf. A branch merged back into trunk keeps its tip in the leaf table -- that class was 939 of the 1,426 leaves the real Fossil repository has.
  • The plink row's isprim flag is not consulted at all. A branch point stays a leaf even though its only child is its *primary* child, because that child is on a different branch.

Index

Constants

View Source
const BranchTagID = 8

BranchTagID is the tagid db/schema.go seeds for 'branch', which is fossil's own TAG_BRANCH; both schemas fix it, so it is a constant rather than a lookup per row of a whole-repository rebuild.

Variables

This section is empty.

Functions

func Check

func Check(q db.Querier, rid libfossil.FslID) error

Check brings rid's leaf row in line with the rule above, inserting or deleting as needed: Section 7.3's incremental check, fossil's leaf_check(). It reads tagxref, so it must run once every branch row it compares is in place; Pending.Apply is how the write paths reach it.

func Rebuild

func Rebuild(q db.Querier) error

Rebuild recomputes the whole leaf table: Section 7.3's full rebuild, the table crosslinking every check-in in a single batch produces. It scans the repository, so only an explicit rebuild calls it (verify.Rebuild through manifest.CrosslinkRebuild); every routine write goes through Pending.

The candidates are the rids a single batch would have scheduled: every rid carrying a branch tagxref row of any type, plus every rid that is a parent in plink. That is NOT "every check-in". A check-in that has a parent but carries no branch tag is never checked, so it is never a leaf; the corrected Section 7.3 states the same rule, and widening the set to plink children changed the leaf table of a real fossil-authored repository (the libfossil corpus holds a tagless check-in with a parent), which `fossil rebuild` does not reproduce.

Taking every plink parent, rather than only the parents of tagged check-ins, adds no member: a parent reached only through untagged children is either untagged itself, and so on the default branch with them, or tagged and a candidate anyway.

The set still differs from "every check-in" in both directions. A parent whose own artifact never arrived has no event row at all, yet fossil does list such a phantom as a leaf when the check-in naming it sits on another branch. In the other direction, a check-in that neither declares nor inherits a branch tag and has no ancestry -- `fossil merge --integrate` writes one of these, a comment and a `T +closed` card and nothing else -- is never checked, and is absent from fossil's leaf table even though the rule would admit it. Drawing candidates from event instead put four such rows in a corpus clone that `fossil rebuild` then removed (issue #193).

Types

type Findings added in v0.14.0

type Findings struct {
	// Stale are rows a recheck has removed: check-ins with a same-branch
	// child whose branch row was written once the child was linked.
	Stale []libfossil.FslID
	// Missing are check-ins a recheck has inserted: each carries a branch
	// row and has no same-branch child, yet has no row.
	Missing []libfossil.FslID
	// Stray are rows no recheck writes: rids that are no candidate at all.
	Stray []libfossil.FslID
}

Findings are the leaf-table errors Audit finds, each in ascending rid order.

func Audit added in v0.14.0

func Audit(q db.Querier, detached map[libfossil.FslID]struct{}) (Findings, error)

Audit checks the leaf table against what Section 7.3 lets a reader decide without knowing the order the artifacts arrived in. The table is not a function of the repository: a check-in keeps its verdict when a child that carries no branch row is linked to it in a later batch, or when a reparent moves a child onto or off it, since neither schedules a recheck. So the incremental table and a full rebuild can differ, and both are consistent. Audit applies the Section 7.2 test, the one Check applies, only where every arrival order agrees:

  • A row is stale when a same-branch child carries a branch row that was written once the child was linked: propagated along the link (srcid 0), or the child's own T card (srcid the child), on a child that carries no parent tag row. Writing it scheduled the child's parents, and that recheck removed the row. A row a control artifact wrote proves nothing, as the tag may have arrived before the check-in it names, and neither does a reparented child, whose link may be newer than its branch row. That holds for a cancelled parent tag too: the cancel re-derives nothing, so the link the reparent wrote stays until a full rebuild re-derives plink from the P-card (fossil's reparent pass reads only tagtype 1 rows, src/manifest.c manifest_crosslink_end).
  • A check-in carrying a branch row of any type is missing when it has no same-branch child, no row, and is not in detached. Its last branch write rechecked it, and after that only a branch write to it or to a child, which schedules it again, or a reparent taking a child away can change its verdict. detached names the check-ins a reparent may have taken a child from: the parents a check-in's P-card or any parent tag naming it lists, that its plink no longer holds. Only those artifacts record them, and a later parent tag replaces an earlier one's row.
  • A row is stray when its rid carries no branch row, appears in no plink row and is not in detached: a file artifact, say, or a rid with no artifact. fossil's leaf_check runs only on a check-in a branch write scheduled and on that check-in's plink parents (src/leaf.c leaf_eventually_check), and leaf_rebuild, behind `fossil leaves --recompute`, inserts only plink children; Check and Rebuild here reach no other rid either.

A check-in with no branch row and no same-branch child may lack a row, being a candidate only as the parent of a tagged check-in, and Audit does not report it.

"Same-branch" is read two ways, and Audit holds a table to a finding only where both agree: each has a same-branch child witnessing a stale row (not necessarily the same child), and neither has one for a missing row. One is the draft's, which Check applies: active branch rows only, and the main-branch setting as it is now. The other is fossil 2.28's leaf_check (fossilSameBranchSQL): a branch row of any type, and the literal trunk. fossil writes its tables that way, departing from Sections 6.1 and 6.4 when main-branch is not trunk or a branch tag was cancelled. A table written here while main-branch was unset reads the default branch that way too once the setting is changed, since changing it rechecks nothing.

type Mark

type Mark struct {
	// contains filtered or unexported fields
}

Mark is a point a schedule can be rolled back to; see Pending.Mark.

type Pending

type Pending struct {
	// contains filtered or unexported fields
}

Pending is Section 7.3's deferred schedule, fossil's needToCheck bag (src/leaf.c leaf_eventually_check, leaf_do_pending_checks): the check-ins a batch must recheck before its leaf table is read.

A check-in is scheduled only when a branch tag of any type is written to it or the propagation walk updates or deletes its branch row (src/tag.c tag_insert and tag_propagate); internal/tag calls Schedule at exactly those writes. Crosslinking a check-in or adding a plink row schedules nothing, so a check-in keeps its verdict when an untagged child is linked to it in a later batch (issue #426).

It is not safe for concurrent use; one batch owns one.

func NewPending

func NewPending() *Pending

NewPending returns an empty schedule.

func (*Pending) Apply

func (p *Pending) Apply(q db.Querier) error

Apply runs the incremental check on every scheduled check-in and on its parents, those it had when scheduled and those it has now. It keeps the schedule: the caller clears it with Reset once the transaction holding the checks has committed, or rolls it back if it did not. The work is bounded by what the batch scheduled, never by the repository.

Checking the parents a check-in has now departs from fossil, whose leaf_eventually_check reads plink only when the branch write happens (src/leaf.c:178-191). The draft defines the single-batch result as the check-ins a branch tag reaches and their parents, whatever order the batch linked them in, and fossil's rule gives a different table in two cases: a tag applied before its target is linked (the target has no plink row yet), and a reparent inside the batch (the new parent was never scheduled). Following the draft here keeps a delta-chain-ordered sweep equal to an ascending one, and matches `fossil rebuild` on the corpus repositories.

func (*Pending) Len

func (p *Pending) Len() int

Len reports how many check-ins the schedule would check now, before Apply adds the parents scheduled check-ins have gained since.

func (*Pending) Mark

func (p *Pending) Mark() Mark

Mark records the schedule's current extent. A caller whose transaction or savepoint rolls back passes it to Rollback, so the branch writes that rolled back do not leave rechecks behind for check-ins the rule never reached.

func (*Pending) Reset

func (p *Pending) Reset()

Reset empties the schedule, as leaf_do_pending_checks does once it has run.

func (*Pending) Rollback

func (p *Pending) Rollback(m Mark)

Rollback forgets everything scheduled since m.

func (*Pending) Schedule

func (p *Pending) Schedule(q db.Querier, rid libfossil.FslID) error

Schedule queues rid and each of its current plink parents, merge parents included, for a recheck. It is leaf_eventually_check(): fossil reads the parents here, at the branch write, so a parent the batch later loses (a reparent) is still checked. Apply goes beyond fossil and also checks the parents rid has by then; see Apply.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL