Artifact 7acf1cf20d55e45cdd0869b3c48502951496c4cb43a6211f95417a2dfb4dcb0f:
- File
internal/checkout/fork.go
— part of check-in
[cd4a11828f]
at
2026-10-12 20:17:22
on branch main
— fix(checkout): make WouldFork fossil's commit-time fork check (#347) (#348)
Since #291 WouldFork was fossil's leaf_ambiguity, which answers no at once for a version that is not a leaf. That is the case a commit forks in: when a pull brings in a child of the checked-out version, the version stops being a leaf, WouldFork said false, and the commit became a second child. EdgeSync's autosync relies on WouldFork to refuse that commit.
WouldFork is now fossil's commit refusal (src/checkin.c "Would fork", taken when !is_a_leaf(vid)): true when the version has a child on its own branch, read from plink as is_a_leaf does, merge children included. Another open leaf elsewhere on the branch no longer counts; fossil commits there, extending the checkout's leaf, and only warns afterwards.
The repo-state draft's Section 7.5 ambiguity test moves to checkout.LeafAmbiguous, and the repostate.7.5.ambiguity-* conformance cases call it. The superseded #291 changelog text and the WouldFork API doc are corrected. (user: [email protected], size: 5716) [annotate] [blame] [check-ins using]
package checkout import ( "database/sql" "errors" "fmt" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/repo" ) // BranchLeaves returns the open leaf RIDs for a named branch: a leaf whose // active branch tag is branch and that carries no 'closed' tag, as fossil's // `leaves` lists them (src/leaf.c leaf_is_closed_sql). A branch with >1 open // leaf is forked. An empty branch name queries trunk. func BranchLeaves(r *repo.Repo, branch string) ([]libfossil.FslID, error) { if r == nil { panic("checkout.BranchLeaves: r must not be nil") } if branch == "" { branch = "trunk" } rows, err := r.DB().Query(` SELECT l.rid FROM leaf l JOIN tagxref tx ON tx.rid = l.rid JOIN tag t ON t.tagid = tx.tagid WHERE t.tagname = 'branch' AND tx.value = ? AND tx.tagtype > 0 AND NOT EXISTS ( SELECT 1 FROM tagxref cx JOIN tag ct ON ct.tagid = cx.tagid WHERE ct.tagname = 'closed' AND cx.rid = l.rid AND cx.tagtype > 0 ) `, branch) if err != nil { return nil, fmt.Errorf("checkout.BranchLeaves: %w", err) } defer rows.Close() var leaves []libfossil.FslID for rows.Next() { var rid int64 if err := rows.Scan(&rid); err != nil { return nil, fmt.Errorf("checkout.BranchLeaves scan: %w", err) } leaves = append(leaves, libfossil.FslID(rid)) } return leaves, rows.Err() } // WouldFork reports whether committing on the checkout's version would // create a fork: the version already has a child on its own branch, so a new // check-in would become a second leaf beside it. This is fossil's commit-time // refusal (src/checkin.c "Would fork", taken when !is_a_leaf(vid)). Another // open leaf elsewhere on the branch does not make a commit fork -- the commit // extends this leaf -- and is reported by LeafAmbiguous instead. A checkout // with no version (an empty repository) cannot fork. func (c *Checkout) WouldFork() (bool, error) { if c == nil { panic("checkout.WouldFork: nil *Checkout") } rid, _, err := c.Version() if err != nil { return false, fmt.Errorf("checkout.WouldFork: %w", err) } if rid <= 0 { return false, nil } return hasSameBranchChild(c.repo, rid) } // hasSameBranchChild reports whether rid has a child check-in on the same // branch, computed from plink as fossil's is_a_leaf (src/leaf.c) does, with // no branch tag meaning trunk. Reading plink rather than the leaf table keeps // the commit check correct even if the leaf table has drifted. func hasSameBranchChild(r *repo.Repo, rid libfossil.FslID) (bool, error) { if rid <= 0 { panic("checkout.hasSameBranchChild: rid must be positive") } var one int err := r.DB().QueryRow(` SELECT 1 FROM plink WHERE pid = ? AND coalesce((SELECT tx.value FROM tagxref tx JOIN tag t ON t.tagid = tx.tagid WHERE t.tagname = 'branch' AND tx.rid = plink.pid AND tx.tagtype > 0), 'trunk') = coalesce((SELECT tx.value FROM tagxref tx JOIN tag t ON t.tagid = tx.tagid WHERE t.tagname = 'branch' AND tx.rid = plink.cid AND tx.tagtype > 0), 'trunk') LIMIT 1 `, int64(rid)).Scan(&one) if errors.Is(err, sql.ErrNoRows) { return false, nil } if err != nil { return false, fmt.Errorf("checkout.hasSameBranchChild: %w", err) } return true, nil } // LeafAmbiguous is fossil's leaf_ambiguity (src/leaf.c) and the repo-state // draft's Section 7.5 ambiguity test: rid must itself be an open leaf, and // another open leaf must share its branch. It answers whether propagating // from rid is underdetermined, not whether a commit on rid would fork (see // WouldFork). func LeafAmbiguous(r *repo.Repo, rid libfossil.FslID) (bool, error) { if r == nil { panic("checkout.LeafAmbiguous: r must not be nil") } if rid <= 0 { panic("checkout.LeafAmbiguous: rid must be positive") } open, err := isOpenLeaf(r, rid) if err != nil { return false, err } if !open { return false, nil } branch, err := branchOf(r, rid) if err != nil { return false, err } leaves, err := BranchLeaves(r, branch) if err != nil { return false, err } for _, leaf := range leaves { if leaf != rid { return true, nil } } return false, nil } // isOpenLeaf reports whether rid is in the leaf table and carries no 'closed' // tag -- fossil's is_a_leaf(rid) AND NOT leaf_is_closed(rid), the precondition // LeafAmbiguous checks before looking for a sibling. func isOpenLeaf(r *repo.Repo, rid libfossil.FslID) (bool, error) { var one int err := r.DB().QueryRow(` SELECT 1 FROM leaf WHERE rid = ? AND NOT EXISTS ( SELECT 1 FROM tagxref cx JOIN tag ct ON ct.tagid = cx.tagid WHERE ct.tagname = 'closed' AND cx.rid = leaf.rid AND cx.tagtype > 0 ) `, int64(rid)).Scan(&one) if errors.Is(err, sql.ErrNoRows) { return false, nil } if err != nil { return false, fmt.Errorf("checkout.isOpenLeaf: %w", err) } return true, nil } // branchOf returns the branch name for the given RID, "trunk" when the // check-in carries no branch tag. A real query error is returned rather than // masked as "trunk", so LeafAmbiguous surfaces it. func branchOf(r *repo.Repo, rid libfossil.FslID) (string, error) { var branch string err := r.DB().QueryRow(` SELECT tx.value FROM tagxref tx JOIN tag t ON t.tagid = tx.tagid WHERE t.tagname = 'branch' AND tx.rid = ? AND tx.tagtype > 0 ORDER BY tx.mtime DESC LIMIT 1 `, int64(rid)).Scan(&branch) if errors.Is(err, sql.ErrNoRows) { return "trunk", nil // no branch tag → trunk } if err != nil { return "", fmt.Errorf("checkout.branchOf: %w", err) } return branch, nil }