A merge preview reports raw paths and objects, so only the indexer computes a named tree or reads packages (ADR 0593 §2)

ModuleTrees answers each module directory's git tree, its manifest and lock digests, and the git objects of the paths its
artifacts name; it no longer computes delivery.NamedTree or reads the mesh packages out of go.sum.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 18:28:22 +01:00
parent 350aca05fb
commit 6f781abb57
4 changed files with 24 additions and 34 deletions
+6 -13
View File
@@ -12,16 +12,17 @@ import (
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
var lockFiles = []string{"go.sum", "package-lock.json"}
// ModuleTrees are the trees, in commit, of every directory of the workspace dir that holds a module.json, by directory
// (the repository's root is "."); for a module whose every artifact names its paths, the named tree of those paths and
// their objects (novox/hq ADR 0499 clause 2). The workspace is at commit, so the manifests and the lock files are read from it. env is
// the environment git runs with (nil for none). Both build modules' fetch steps answer it for a merge.
// (the repository's root is "."): the directory's git tree, the digests of its manifest and lock files, and, for a module
// whose every artifact names its paths, those paths' git objects (novox/hq ADR 0499 clause 2). It is raw: the indexer alone
// computes a module's named tree and reads its mesh packages from it (novox/hq ADR 0593 §2). The workspace is at commit, so
// the manifests and the lock files are read from it. env is the environment git runs with (nil for none). Both build
// modules' fetch steps answer it for a merge.
func ModuleTrees(ctx context.Context, run forgekey.Runner, env []string, dir, commit string) (map[string]sdkstep.ModuleTree, error) {
r := run(ctx, env, "git", "-C", dir, "ls-files", "--", "module.json", "*/module.json")
if r.Status != 0 || r.Err != "" {
@@ -56,9 +57,6 @@ func ModuleTrees(ctx context.Context, run forgekey.Runner, env []string, dir, co
mt.Locks = map[string]string{}
}
mt.Locks[lock] = sha256Hex(raw)
if lock == "go.sum" {
mt.Packages = delivery.MeshPackagesOf(raw) // the mesh packages it uses (novox/hq ADR 0573 §2)
}
}
}
trees[mdir] = mt
@@ -66,7 +64,7 @@ func ModuleTrees(ctx context.Context, run forgekey.Runner, env []string, dir, co
return trees, nil
}
// narrow sets mt's Paths and its named Tree when the module's manifest names paths on every artifact (novox/hq ADR 0499
// narrow sets mt's Paths when the module's manifest names paths on every artifact (novox/hq ADR 0499
// clause 2): one `git ls-tree -z` gives each named path's object at commit. The merge preview decodes the manifest here
// (catalogue.Decode: a build step judges no manifest, novox/hq ADR 0578 §3), and no other build step reads paths from one
// (ADR 0495 clause 1). A manifest that does not decode is not narrowed: the indexer gives it none. A named path absent at
@@ -105,11 +103,6 @@ func narrow(ctx context.Context, run forgekey.Runner, env []string, dir, commit,
}
mt.Paths[p] = object
}
named, err := delivery.NamedTree(mt.Manifest, mt.Paths)
if err != nil {
return fmt.Errorf("the module %s: %w", mdir, err)
}
mt.Tree = named
return nil
}
@@ -5,6 +5,7 @@ package buildstep
import (
"context"
sdkstep "git.novox.be/novox/mesh-sdk/go/buildstep"
"reflect"
"strings"
"testing"
@@ -12,9 +13,9 @@ import (
)
// On a real repository shaped like mesh-sdk (novox/hq ADR 0499 clauses 2 and 6), a commit touching only the Go package
// leaves the TypeScript package's tree value equal, and a commit touching its source changes it; a module that names no
// path keeps its directory's tree id.
func TestAGoOnlyCommitLeavesTheNamedTreeEqualOnRealGit(t *testing.T) {
// leaves the TypeScript package's named objects equal, and a commit touching its source changes them; the answer is raw
// (Tree is always the directory's git tree, the indexer computes the named one: ADR 0593 §2).
func TestAGoOnlyCommitLeavesTheNamedObjectsEqualOnRealGit(t *testing.T) {
repo := t.TempDir()
realGit(t, repo, "init", "-q", "-b", "main")
writeFile(t, repo, "module.json", `{"module":"mesh-sdk","version":"1","build":{"artifacts":[`+
@@ -40,8 +41,8 @@ func TestAGoOnlyCommitLeavesTheNamedTreeEqualOnRealGit(t *testing.T) {
return got
}
first := trees(commit("first"))
if !strings.HasPrefix(first["."].Tree, "named:") || len(first["."].Paths) != 5 {
t.Fatalf("the root module was not narrowed: %+v", first["."])
if want := realGit(t, repo, "rev-parse", "HEAD^{tree}"); first["."].Tree != want || strings.HasPrefix(first["."].Tree, "named:") || len(first["."].Paths) != 5 {
t.Fatalf("the root module: %+v, want its git tree %s and five named objects", first["."], want)
}
if want := realGit(t, repo, "rev-parse", "HEAD:other"); first["other"].Tree != want || first["other"].Paths != nil {
t.Fatalf("a module naming no path: %+v, want its directory's tree %s", first["other"], want)
@@ -49,14 +50,14 @@ func TestAGoOnlyCommitLeavesTheNamedTreeEqualOnRealGit(t *testing.T) {
writeFile(t, repo, "go/delivery/x.go", "package delivery\n\nconst B = 2\n")
goOnly := trees(commit("go only"))
if goOnly["."].Tree != first["."].Tree {
t.Fatalf("a Go-only commit changed the TypeScript package's tree: %s, was %s", goOnly["."].Tree, first["."].Tree)
if !reflect.DeepEqual(goOnly["."].Paths, first["."].Paths) {
t.Fatalf("a Go-only commit changed the TypeScript package's objects: %v, were %v", goOnly["."].Paths, first["."].Paths)
}
writeFile(t, repo, "src/index.ts", "export const a = 2\n")
src := trees(commit("src"))
if src["."].Tree == first["."].Tree {
t.Fatal("a commit to src/ left the TypeScript package's tree equal")
if reflect.DeepEqual(src["."].Paths, first["."].Paths) {
t.Fatal("a commit to src/ left the TypeScript package's objects equal")
}
if src["."].Paths["src"] != realGit(t, repo, "rev-parse", "HEAD:src") {
t.Fatalf("the object of src is %s, git says %s", src["."].Paths["src"], realGit(t, repo, "rev-parse", "HEAD:src"))
@@ -6,13 +6,12 @@ import (
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
// A module whose every artifact names its paths takes its tree from them (novox/hq ADR 0499 clause 2): ModuleTrees asks
// git for each named path's object at the commit, keeps them in Paths and answers their delivery.NamedTree as Tree; a
// module naming none keeps its directory's tree, as before.
// git for each named path's object at the commit and keeps them in Paths; Tree stays the directory's git tree, for the
// indexer computes the named tree (novox/hq ADR 0593 §2). A module naming none has no Paths.
func TestModuleTreesNarrowsAModuleWhoseArtifactsNameTheirPaths(t *testing.T) {
root := t.TempDir()
sdk := `{"module":"mesh-sdk","version":"1","build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript","paths":["src","package.json"]}]}}`
@@ -38,9 +37,8 @@ func TestModuleTreesNarrowsAModuleWhoseArtifactsNameTheirPaths(t *testing.T) {
t.Fatal(err)
}
paths := map[string]string{"src": strings.Repeat("a", 40), "package.json": strings.Repeat("b", 40)}
want, _ := delivery.NamedTree(hexOf(sdk), paths)
if g := got["."]; g.Tree != want || len(g.Paths) != 2 || g.Paths["src"] != paths["src"] || g.Paths["package.json"] != paths["package.json"] {
t.Fatalf("the root module = %+v, want tree %s and paths %v", g, want, paths)
if g := got["."]; g.Tree != "tree-of-"+merge+"^{tree}" || strings.HasPrefix(g.Tree, "named:") || len(g.Paths) != 2 || g.Paths["src"] != paths["src"] || g.Paths["package.json"] != paths["package.json"] {
t.Fatalf("the root module = %+v, want its git tree and paths %v", g, paths)
}
if len(lsTree) != 1 || lsTree[0] != merge+" -- package.json src" {
t.Fatalf("git ls-tree ran as %v", lsTree)
@@ -103,8 +101,7 @@ func TestModuleTreesNarrowsAManifestFallingBackToAPredefinedKey(t *testing.T) {
if err != nil {
t.Fatal(err)
}
want, _ := delivery.NamedTree(hexOf(raw), map[string]string{"src": strings.Repeat("a", 40)})
if g := got["."]; g.Tree != want || g.Paths["src"] == "" {
t.Fatalf("the module = %+v, want the named tree %s", g, want)
if g := got["."]; g.Tree != "whole" || g.Paths["src"] != strings.Repeat("a", 40) {
t.Fatalf("the module = %+v, want its git tree and the object of src", g)
}
}
@@ -57,9 +57,8 @@ func TestModuleTreesAnswersTreeManifestAndLocksPerModuleDirectory(t *testing.T)
t.Fatal(err)
}
want := map[string]sdkstep.ModuleTree{
".": {Tree: "tree-of-" + merge + "^{tree}", Manifest: hexOf(`{"root":1}`), Locks: map[string]string{"go.sum": hexOf("rootsum")}},
"modules/a": {Tree: "tree-of-" + merge + ":modules/a", Manifest: hexOf(`{"a":1}`), Locks: map[string]string{"go.sum": hexOf(asum), "package-lock.json": hexOf("alock")},
Packages: []string{"git.novox.be/novox/mesh-sdk/go"}}, // issue 1585: the mesh packages its go.sum names
".": {Tree: "tree-of-" + merge + "^{tree}", Manifest: hexOf(`{"root":1}`), Locks: map[string]string{"go.sum": hexOf("rootsum")}},
"modules/a": {Tree: "tree-of-" + merge + ":modules/a", Manifest: hexOf(`{"a":1}`), Locks: map[string]string{"go.sum": hexOf(asum), "package-lock.json": hexOf("alock")}},
"modules/b": {Tree: "tree-of-" + merge + ":modules/b", Manifest: hexOf(`{"b":1}`)},
}
if !reflect.DeepEqual(got, want) {