Files
rose-ash/lib/git/tests/dag.sx
giles eda6806989 sx-git Phase 3: commit DAG — log/ancestry/merge-base SX-side + Datalog bridge (TDD)
Topo log = reverse DFS postorder over parent edges from commit objects;
reflexive is-ancestor?, all-LCA merge-bases (criss-cross verified). Datalog
bridge exports (git-parent child parent) facts under a minimal 2-rule
ancestor closure, cross-checked against the SX walk. 30/30, total 106/106.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 12:07:06 +00:00

173 lines
4.7 KiB
Plaintext

; Phase 3 — dag: log (topo), ancestry, is-ancestor?, reachability, merge-base,
; and the minimal Datalog ancestor-closure bridge.
; Fixtures: a linear chain, a diamond merge, a criss-cross (two LCAs),
; and two unrelated roots.
(define gdt-db (persist/mem-backend))
(define gdt (git/repo gdt-db))
(define
gdt-commit!
(fn
(msg parents)
(git/write gdt (git/commit (git/write-blob gdt msg) parents {:message msg}))))
; linear chain c1 <- c2 <- c3
(define gdt-c1 (gdt-commit! "c1" (list)))
(define gdt-c2 (gdt-commit! "c2" (list gdt-c1)))
(define gdt-c3 (gdt-commit! "c3" (list gdt-c2)))
; diamond: b1 <- p, b1 <- q, m = merge(p q)
(define gdt-b1 (gdt-commit! "b1" (list)))
(define gdt-p (gdt-commit! "p" (list gdt-b1)))
(define gdt-q (gdt-commit! "q" (list gdt-b1)))
(define gdt-m (gdt-commit! "m" (list gdt-p gdt-q)))
; criss-cross: base <- a, base <- b; x = merge(a b), y = merge(a b) via
; distinct messages so x != y
(define gdt-base (gdt-commit! "base" (list)))
(define gdt-a (gdt-commit! "a" (list gdt-base)))
(define gdt-b (gdt-commit! "b" (list gdt-base)))
(define gdt-x (gdt-commit! "x" (list gdt-a gdt-b)))
(define gdt-y (gdt-commit! "y" (list gdt-a gdt-b)))
; unrelated root
(define gdt-lone (gdt-commit! "lone" (list)))
; ---- parents ----
(git-test
"parents of a root commit"
(= (git/parents gdt gdt-c1) (list))
true)
(git-test
"parents of a merge commit"
(= (git/parents gdt gdt-m) (list gdt-p gdt-q))
true)
(git-test
"parents of a non-commit cid"
(= (git/parents gdt "sx1:junk") (list))
true)
; ---- reachability ----
(git-test
"reachable includes self"
(git/dag-member? gdt-c3 (git/reachable gdt gdt-c3))
true)
(git-test
"reachable spans the chain"
(= (git/reachable gdt gdt-c3) (list gdt-c1 gdt-c2 gdt-c3))
true)
(git-test
"reachable covers both diamond legs"
(len (git/reachable gdt gdt-m))
4)
(git-test
"reachable-all merges from several heads without duplicates"
(len (git/reachable-all gdt (list gdt-x gdt-y)))
5)
; ---- log: children before parents ----
(git-test
"log on a chain is newest-first"
(= (git/log gdt gdt-c3) (list gdt-c3 gdt-c2 gdt-c1))
true)
(git-test
"log messages read newest-first"
(= (git/log-messages gdt gdt-c3) (list "c3" "c2" "c1"))
true)
(git-test
"log of a diamond is a valid topo order"
(= (git/log gdt gdt-m) (list gdt-m gdt-q gdt-p gdt-b1))
true)
(git-test
"log of a root is just the root"
(= (git/log gdt gdt-c1) (list gdt-c1))
true)
; ---- ancestors / is-ancestor? ----
(git-test
"ancestors excludes self"
(git/dag-member? gdt-c3 (git/ancestors gdt gdt-c3))
false)
(git-test
"ancestors of the chain tip"
(= (git/ancestors gdt gdt-c3) (list gdt-c1 gdt-c2))
true)
(git-test
"ancestors of a merge include both legs and the base"
(len (git/ancestors gdt gdt-m))
3)
(git-test
"is-ancestor? along the chain"
(git/is-ancestor? gdt gdt-c1 gdt-c3)
true)
(git-test
"is-ancestor? is directed"
(git/is-ancestor? gdt gdt-c3 gdt-c1)
false)
(git-test
"is-ancestor? is reflexive"
(git/is-ancestor? gdt gdt-c2 gdt-c2)
true)
(git-test
"is-ancestor? false across unrelated history"
(git/is-ancestor? gdt gdt-c1 gdt-lone)
false)
; ---- merge-base ----
(git-test
"merge-base of the diamond legs is the base"
(git/merge-base gdt gdt-p gdt-q)
gdt-b1)
(git-test
"merge-base when one side is an ancestor is that side"
(git/merge-base gdt gdt-c1 gdt-c3)
gdt-c1)
(git-test "merge-base with self" (git/merge-base gdt gdt-c3 gdt-c3) gdt-c3)
(git-test
"merge-base of unrelated commits is nil"
(git/merge-base gdt gdt-c3 gdt-lone)
nil)
(git-test
"merge-base is symmetric"
(equal? (git/merge-base gdt gdt-p gdt-q) (git/merge-base gdt gdt-q gdt-p))
true)
(git-test
"criss-cross yields BOTH best common ancestors"
(=
(git/merge-bases gdt gdt-x gdt-y)
(artdag/sort-strings (list gdt-a gdt-b)))
true)
(git-test
"dominated common ancestor is not a merge-base"
(git/dag-member? gdt-base (git/merge-bases gdt gdt-x gdt-y))
false)
; ---- Datalog bridge ----
(git-test
"dag-facts exports one fact per parent edge"
(len (git/dag-facts gdt (list gdt-m)))
4)
(git-test
"datalog ancestors match the SX-side walk"
(let
((db (git/dag-db gdt (list gdt-m))))
(=
(git/ancestors-dl db gdt-m)
(artdag/sort-strings (git/ancestors gdt gdt-m))))
true)
(git-test
"datalog is-ancestor? positive"
(git/is-ancestor-dl? (git/dag-db gdt (list gdt-m)) gdt-b1 gdt-m)
true)
(git-test
"datalog is-ancestor? negative"
(git/is-ancestor-dl? (git/dag-db gdt (list gdt-m)) gdt-m gdt-b1)
false)
(git-test
"datalog closure spans the chain"
(=
(git/ancestors-dl (git/dag-db gdt (list gdt-c3)) gdt-c3)
(artdag/sort-strings (list gdt-c1 gdt-c2)))
true)