Files
rose-ash/lib/git/export.sx
giles e228d462eb sx-git extension: git-wire export adapter — byte-exact loose objects + SHA-1 (TDD)
lib/git/sha1.sx: SHA-1 in pure SX over host bitwise prims (FIPS vectors +
multi-block verified). lib/git/export.sx: native objects -> git payloads
"<type> <len>\0<body>" with real git identity, golden-verified against git
CLI (hash-object/mktree/commit-tree/mktag with pinned idents): tree entry
sorting with dirs keyed "name/", raw 20-byte child shas, "40000" subtree
mode, :mode overrides, deterministic ident defaults, trailing-newline
message rule. export-closure/export-set emit a host-writable object table.
Adapter-at-the-edge: native model untouched; zlib/packfiles remain host-side
concerns. 25/25, total 252/252.

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

226 lines
6.4 KiB
Plaintext

; lib/git/export.sx — git-wire EXPORT ADAPTER (flagged extension).
; Converts native sx-git objects into byte-exact git loose-object payloads:
; "<type> <len>\0<body>" with real git SHA-1 identity, golden-verified
; against git CLI output. This is an adapter at the edge — the base model
; stays native-CID (sx1:/sha256, typed extensible dicts). zlib/packfiles are
; host-side concerns and stay out of scope: the adapter emits type+bytes+sha1.
; Field mapping (commit/tag idents): :author/:email/:time/:tz with committer
; overrides :committer/:committer-email/:committer-time/:committer-tz;
; defaults email="<author>@sx", time=0, tz="+0000". Messages get a trailing
; newline if missing. Extra native fields do NOT survive export (git wire
; has nowhere to put them) — that loss is the point of native-first.
; Requires: lib/git/object.sx, lib/git/sha1.sx, lib/artdag/dag.sx.
(define git/export-nul (list->string (list (integer->char 0))))
; ---- tree entry details ----
; raw tree bytes use "40000" for subtrees (no leading zero); files default
; to 100644 unless the entry carries an explicit :mode (e.g. "100755")
(define
git/export-mode
(fn
(e)
(if
(has-key? e :mode)
(get e :mode)
(if (equal? (get e :kind) "tree") "40000" "100644"))))
; git sorts tree entries by name bytes with directories keyed as "name/"
(define
git/export-sortkey
(fn
(tree name)
(if
(equal? (git/entry-kind (git/tree-entry-for tree name)) "tree")
(str name "/")
name)))
(define
git/export-ins
(fn
(tree sorted n)
(cond
((empty? sorted) (list n))
((artdag/str<? (git/export-sortkey tree n) (git/export-sortkey tree (first sorted)))
(cons n sorted))
(else (cons (first sorted) (git/export-ins tree (rest sorted) n))))))
(define
git/export-sort-names
(fn
(tree names)
(reduce (fn (acc n) (git/export-ins tree acc n)) (list) names)))
; ---- ident + message formatting ----
(define git/export-or (fn (v dflt) (if (equal? v nil) dflt v)))
(define
git/export-author-ident
(fn
(obj)
(let
((name (git/export-or (get obj :author) "sx")))
(str
name
" <"
(git/export-or (get obj :email) (str name "@sx"))
"> "
(git/export-or (get obj :time) 0)
" "
(git/export-or (get obj :tz) "+0000")))))
(define
git/export-committer-ident
(fn
(obj)
(let
((name (git/export-or (get obj :committer) (git/export-or (get obj :author) "sx"))))
(str
name
" <"
(git/export-or
(get obj :committer-email)
(git/export-or (get obj :email) (str name "@sx")))
"> "
(git/export-or
(get obj :committer-time)
(git/export-or (get obj :time) 0))
" "
(git/export-or
(get obj :committer-tz)
(git/export-or (get obj :tz) "+0000"))))))
(define
git/export-message
(fn
(obj)
(let
((m (git/export-or (get obj :message) "")))
(let
((n (string-length m)))
(if
(and
(> n 0)
(equal? (substring m (- n 1) n) "\n"))
m
(str m "\n"))))))
; ---- object payloads ----
(define
git/export-entry
(fn
(type body)
(let
((full (str type " " (string-length body) git/export-nul body)))
{:type type :sha1 (git/sha1-hex full) :bytes full})))
(define
git/export-tree-body
(fn
(tree table)
(reduce
(fn
(acc name)
(let
((e (git/tree-entry-for tree name)))
(str
acc
(git/export-mode e)
" "
name
git/export-nul
(git/hex->raw (get (get table (git/entry-cid e)) :sha1)))))
""
(git/export-sort-names tree (git/tree-names tree)))))
(define
git/export-commit-body
(fn
(obj table)
(str
"tree "
(get (get table (git/commit-tree obj)) :sha1)
"\n"
(reduce
(fn (acc p) (str acc "parent " (get (get table p) :sha1) "\n"))
""
(git/commit-parents obj))
"author "
(git/export-author-ident obj)
"\n"
"committer "
(git/export-committer-ident obj)
"\n"
"\n"
(git/export-message obj))))
(define
git/export-tag-body
(fn
(obj table)
(str
"object "
(get (get table (git/tag-target obj)) :sha1)
"\n"
"type "
(get (get table (git/tag-target obj)) :type)
"\n"
"tag "
(git/tag-name obj)
"\n"
"tagger "
(git/export-author-ident obj)
"\n"
"\n"
(git/export-message obj))))
; ---- recursive closure export: table cid -> {:type :bytes :sha1} ----
(define
git/export-into
(fn
(repo cid table)
(if
(has-key? table cid)
table
(let
((obj (git/read repo cid)))
(cond
((git/blob? obj)
(assoc table cid (git/export-entry "blob" (git/blob-data obj))))
((git/tree? obj)
(let
((t2 (reduce (fn (tb name) (git/export-into repo (git/entry-cid (git/tree-entry-for obj name)) tb)) table (git/tree-names obj))))
(assoc
t2
cid
(git/export-entry "tree" (git/export-tree-body obj t2)))))
((git/commit? obj)
(let
((t2 (git/export-into repo (git/commit-tree obj) (reduce (fn (tb p) (git/export-into repo p tb)) table (git/commit-parents obj)))))
(assoc
t2
cid
(git/export-entry "commit" (git/export-commit-body obj t2)))))
((git/tag? obj)
(let
((t2 (git/export-into repo (git/tag-target obj) table)))
(assoc
t2
cid
(git/export-entry "tag" (git/export-tag-body obj t2)))))
(else table))))))
(define
git/export-closure
(fn (repo cid) (git/export-into repo cid {})))
; export one object (with its dependencies): {:type :bytes :sha1}
(define git/export (fn (repo cid) (get (git/export-closure repo cid) cid)))
(define git/export-sha (fn (repo cid) (get (git/export repo cid) :sha1)))
; host-writable set: {:head <sha1> :objects {sha1 -> {:type :bytes}}}
(define
git/export-set
(fn (repo cid) (let ((table (git/export-closure repo cid))) {:head (get (get table cid) :sha1) :objects (reduce (fn (acc c) (let ((e (get table c))) (assoc acc (get e :sha1) {:type (get e :type) :bytes (get e :bytes)}))) {} (keys table))})))