A changed file touches exactly the modules whose build reads it (hq issue 280, ADR 0237)
The builder reads a module's own directory (the repository for one built from its root) and a repository its recipe packages, nothing else. A file in no module's directory was read as shared code and rebuilt everything built from the repository: 103 modules for a merge-check.sh added at the catalogue's root. It now touches nothing, in the merge handler, the release planner and the pull request's check alike, and the gate says so.
This commit is contained in:
@@ -22,10 +22,11 @@ import (
|
|||||||
//
|
//
|
||||||
// **The mesh's module graph decides, not the repository.** The controller holds the graph — every module,
|
// **The mesh's module graph decides, not the repository.** The controller holds the graph — every module,
|
||||||
// the repository and directory it is built from — and maps the pull request's changed paths onto it by
|
// the repository and directory it is built from — and maps the pull request's changed paths onto it by
|
||||||
// the rule the merge handler uses (issue 278): a changed file inside a directory holding a module.json at
|
// the rule the merge handler and the release planner use (whatTheMergeTouched, issue 280): **a changed file
|
||||||
// the head is that module's, held or not; a file in no such directory, in a repository modules are built
|
// touches exactly the modules whose build reads it** — a module's own directory (the whole repository for
|
||||||
// from into that branch, is shared code, and touches every module built from it there. A directory the
|
// one built from its root), or a repository its recipe packages. A file no build reads — a script at the
|
||||||
// change adds a module in, which the graph does not hold yet, is a new module and is checked too.
|
// root, a README — touches no module. A directory the change adds a module.json in, which the graph does
|
||||||
|
// not hold yet (said by the head, issue 278), is a new module and is checked too.
|
||||||
//
|
//
|
||||||
// - touches a module: the build seat runs **the gate** — `mesh/merge-gate`, the touched manifests, every
|
// - touches a module: the build seat runs **the gate** — `mesh/merge-gate`, the touched manifests, every
|
||||||
// machine composed with the change, the replays — and the repository's own merge-check.sh beside it;
|
// machine composed with the change, the replays — and the repository's own merge-check.sh beside it;
|
||||||
|
|||||||
@@ -41,8 +41,11 @@ func TestThePullRequestIsMappedOntoTheModuleGraph(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"one module's own files", pull("novox", "mesh-catalog", "main", []string{"modules/gitea/index.ts"}, "modules/gitea"),
|
{"one module's own files", pull("novox", "mesh-catalog", "main", []string{"modules/gitea/index.ts"}, "modules/gitea"),
|
||||||
"gitea", "", "modules/gitea/module.json", true, ""},
|
"gitea", "", "modules/gitea/module.json", true, ""},
|
||||||
{"shared code touches every module built from the repository",
|
{"a file no module's build reads touches no module (issue 280)",
|
||||||
pull("novox", "mesh-catalog", "main", []string{"tsconfig.json"}), "gitea,keycloak,nats", "",
|
pull("novox", "mesh-catalog", "main", []string{"merge-check.sh", "README.md"}), "", "", "", true, ""},
|
||||||
|
{"files the announcer could not list: everything built from it",
|
||||||
|
link.PullUpdated{Owner: "novox", Repo: "mesh-catalog", Base: "main", PathsTruncated: true,
|
||||||
|
Paths: []string{"README.md"}}, "gitea,keycloak,nats", "",
|
||||||
"modules/gitea/module.json,modules/keycloak/module.json,modules/nats/module.json", true, ""},
|
"modules/gitea/module.json,modules/keycloak/module.json,modules/nats/module.json", true, ""},
|
||||||
{"a new module, said by the head", pull("novox", "mesh-catalog", "main",
|
{"a new module, said by the head", pull("novox", "mesh-catalog", "main",
|
||||||
[]string{"modules/newmod/index.ts", "modules/newmod/module.json"}, "modules/newmod"),
|
[]string{"modules/newmod/index.ts", "modules/newmod/module.json"}, "modules/newmod"),
|
||||||
|
|||||||
@@ -108,9 +108,9 @@ type mergeComposed struct {
|
|||||||
type mergeWidthOf struct {
|
type mergeWidthOf struct {
|
||||||
Modules []string `json:"modules"`
|
Modules []string `json:"modules"`
|
||||||
Tiers int `json:"tiers"`
|
Tiers int `json:"tiers"`
|
||||||
// Shared is the changed paths read as shared code: in no directory of a module the mesh holds, so
|
// Unread is the changed paths no module's build reads — in no module's directory — which rebuild
|
||||||
// everything built from the repository is rebuilt for them.
|
// nothing (issue 280): said, so a reader sees why a change to them moves no module.
|
||||||
Shared []string `json:"shared,omitempty"`
|
Unread []string `json:"unread,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// wideRebuild is how many modules a rebuild may take before the gate says so as a warning.
|
// wideRebuild is how many modules a rebuild may take before the gate says so as a warning.
|
||||||
@@ -312,11 +312,11 @@ func judgeChange(ctx context.Context, in mergeCheckInput) (mergeVerdict, error)
|
|||||||
v.Notes = append(v.Notes, "the width of the rebuild could not be worked out: "+err.Error())
|
v.Notes = append(v.Notes, "the width of the rebuild could not be worked out: "+err.Error())
|
||||||
} else {
|
} else {
|
||||||
v.Width = &w
|
v.Width = &w
|
||||||
if len(w.Shared) > 0 && len(w.Modules) > wideRebuild {
|
if len(w.Unread) > 0 {
|
||||||
v.Warnings = append(v.Warnings, fmt.Sprintf("a merge rebuilds %d module(s), because %s read as shared "+
|
v.Notes = append(v.Notes, fmt.Sprintf("%s read by no module's build, and rebuild nothing (issue 280)",
|
||||||
"code: in no directory of a module the mesh holds (issue 278)", len(w.Modules),
|
readableList(w.Unread)))
|
||||||
readableList(w.Shared)))
|
}
|
||||||
} else if len(w.Modules) > wideRebuild {
|
if len(w.Modules) > wideRebuild {
|
||||||
v.Warnings = append(v.Warnings, fmt.Sprintf("a merge rebuilds %d module(s) in %d tier(s)",
|
v.Warnings = append(v.Warnings, fmt.Sprintf("a merge rebuilds %d module(s) in %d tier(s)",
|
||||||
len(w.Modules), w.Tiers))
|
len(w.Modules), w.Tiers))
|
||||||
}
|
}
|
||||||
@@ -1016,7 +1016,8 @@ func rebuildWidth(f snapshot.Facts, repository string, paths []string, tree stri
|
|||||||
}
|
}
|
||||||
sort.Strings(w.Modules)
|
sort.Strings(w.Modules)
|
||||||
}
|
}
|
||||||
// The paths read as shared: in no directory of a module the mesh holds from this repository.
|
// The paths no build reads: in no directory of a module the mesh holds from this repository, or one
|
||||||
|
// the tree holds.
|
||||||
var dirs []string
|
var dirs []string
|
||||||
for _, e := range from {
|
for _, e := range from {
|
||||||
if d := strings.Trim(e.Source.Path, "/"); d != "" {
|
if d := strings.Trim(e.Source.Path, "/"); d != "" {
|
||||||
@@ -1032,7 +1033,7 @@ func rebuildWidth(f snapshot.Facts, repository string, paths []string, tree stri
|
|||||||
inModule = inModule || strings.HasPrefix(p, d)
|
inModule = inModule || strings.HasPrefix(p, d)
|
||||||
}
|
}
|
||||||
if !inModule && len(dirs) > 0 {
|
if !inModule && len(dirs) > 0 {
|
||||||
w.Shared = append(w.Shared, p)
|
w.Unread = append(w.Unread, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return w, nil
|
return w, nil
|
||||||
|
|||||||
@@ -180,26 +180,32 @@ func TestAModuleAMachineRunsRemovedFromItsSourceFails(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **Issue 278**: a file of a module nobody holds read as shared code, and the merge rebuilt the
|
// **Issues 278 and 280**: a file in no held module's directory read as shared code, and a merge rebuilt the
|
||||||
// catalogue. The gate says how wide a merge's rebuild is, and warns when shared code makes it wide.
|
// catalogue. A file is a module's only by being inside it — no build reads one outside — so it rebuilds
|
||||||
|
// nothing, and the gate says why; a merge that does rebuild widely is warned of.
|
||||||
func TestIssue278AWideRebuildIsSaidBeforeTheMerge(t *testing.T) {
|
func TestIssue278AWideRebuildIsSaidBeforeTheMerge(t *testing.T) {
|
||||||
f, manifests := catalogueMesh(t)
|
f, manifests := catalogueMesh(t)
|
||||||
was := wideRebuild
|
was := wideRebuild
|
||||||
wideRebuild = 2
|
wideRebuild = 2
|
||||||
t.Cleanup(func() { wideRebuild = was })
|
t.Cleanup(func() { wideRebuild = was })
|
||||||
v := gateJudged(t, f, aTree(t, manifests), "modules/showcase/index.ts")
|
for _, file := range []string{"modules/showcase/index.ts", "merge-check.sh", "README.md"} {
|
||||||
if v.Width == nil || len(v.Width.Modules) < 5 || len(v.Width.Shared) != 1 {
|
v := gateJudged(t, f, aTree(t, manifests), file)
|
||||||
t.Fatalf("the width reads %+v", v.Width)
|
if v.Width == nil || len(v.Width.Modules) != 0 || len(v.Width.Unread) != 1 || v.Verdict != "pass" {
|
||||||
|
t.Fatalf("%s, read by no build, reads %+v, %s", file, v.Width, v.Verdict)
|
||||||
|
}
|
||||||
|
if !strings.Contains(v.Report(), "read by no module's build") {
|
||||||
|
t.Errorf("why %s rebuilds nothing is not said:\n%s", file, v.Report())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if v.Verdict != "warning" || !strings.Contains(v.Report(), "shared") {
|
v := gateJudged(t, f, aTree(t, manifests), "modules/album/x.ts", "modules/objects/x.go", "modules/resolver/x.go")
|
||||||
t.Fatalf("a rebuild of everything for one shared file is not said:\n%s", v.Report())
|
if v.Width == nil || len(v.Width.Modules) < 3 || v.Verdict != "warning" {
|
||||||
|
t.Fatalf("a rebuild wider than the bound is not warned of: %+v, %s", v.Width, v.Verdict)
|
||||||
}
|
}
|
||||||
// The same file, with the reference module's definition in the tree: its directory is a module, held
|
// With the reference module's definition in the tree, its directory is a module, held or not.
|
||||||
// or not, and the file is its business alone (the fix of 278, read from the tree as the announcer does).
|
|
||||||
withShowcase := withEdit(manifests, "showcase", `{"module":"showcase","version":"1"}`)
|
withShowcase := withEdit(manifests, "showcase", `{"module":"showcase","version":"1"}`)
|
||||||
v = gateJudged(t, f, aTree(t, withShowcase), "modules/showcase/index.ts")
|
v = gateJudged(t, f, aTree(t, withShowcase), "modules/showcase/index.ts")
|
||||||
if v.Width == nil || len(v.Width.Modules) != 0 || len(v.Width.Shared) != 0 {
|
if v.Width == nil || len(v.Width.Modules) != 0 || len(v.Width.Unread) != 0 {
|
||||||
t.Fatalf("a file of a module nobody holds still reads as shared: %+v", v.Width)
|
t.Fatalf("a file of a module nobody holds reads %+v", v.Width)
|
||||||
}
|
}
|
||||||
v = gateJudged(t, f, aTree(t, manifests), "modules/album/module.json")
|
v = gateJudged(t, f, aTree(t, manifests), "modules/album/module.json")
|
||||||
if v.Width == nil || strings.Join(v.Width.Modules, ",") != "album" || v.Verdict != "pass" {
|
if v.Width == nil || strings.Join(v.Width.Modules, ",") != "album" || v.Verdict != "pass" {
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func TestAMergeRebuildsTheModulesItChanged(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"one module's own files", merge([]string{"modules/gitea/index.ts", "modules/gitea/client.ts"}, false), "gitea"},
|
{"one module's own files", merge([]string{"modules/gitea/index.ts", "modules/gitea/client.ts"}, false), "gitea"},
|
||||||
{"two modules' files", merge([]string{"modules/gitea/index.ts", "modules/keycloak/module.json"}, false), "gitea,keycloak"},
|
{"two modules' files", merge([]string{"modules/gitea/index.ts", "modules/keycloak/module.json"}, false), "gitea,keycloak"},
|
||||||
{"a file they share", merge([]string{"tsconfig.json"}, false), "gitea,keycloak"},
|
{"a file at the root no build reads (issue 280)", merge([]string{"tsconfig.json"}, false), ""},
|
||||||
{"a module the mesh does not hold", merge([]string{"modules/plex/index.ts"}, false), ""},
|
{"a module the mesh does not hold", merge([]string{"modules/plex/index.ts"}, false), ""},
|
||||||
{"nothing said about the files", merge(nil, false), "gitea,keycloak"},
|
{"nothing said about the files", merge(nil, false), "gitea,keycloak"},
|
||||||
{"more files than were listed", merge([]string{"modules/gitea/index.ts"}, true), "gitea,keycloak"},
|
{"more files than were listed", merge([]string{"modules/gitea/index.ts"}, true), "gitea,keycloak"},
|
||||||
@@ -153,9 +153,9 @@ func TestAMergeRebuildsTheModulesItChanged(t *testing.T) {
|
|||||||
{"a new module beside a held one", merge([]string{"modules/gitea/x", "modules/newmod/module.json"}, false), "gitea"},
|
{"a new module beside a held one", merge([]string{"modules/gitea/x", "modules/newmod/module.json"}, false), "gitea"},
|
||||||
{"a new module's other files", merge([]string{"modules/newmod/index.ts", "modules/newmod/module.json"}, false), ""},
|
{"a new module's other files", merge([]string{"modules/newmod/index.ts", "modules/newmod/module.json"}, false), ""},
|
||||||
{"a module removed", merge([]string{"modules/gone/module.json"}, false), ""},
|
{"a module removed", merge([]string{"modules/gone/module.json"}, false), ""},
|
||||||
{"a directory with no manifest", merge([]string{"modules/lib/x.go"}, false), "gitea,keycloak"},
|
{"a directory with no manifest", merge([]string{"modules/lib/x.go"}, false), ""},
|
||||||
{"a file directly among the modules", merge([]string{"modules/README.md"}, false), "gitea,keycloak"},
|
{"a file directly among the modules", merge([]string{"modules/README.md"}, false), ""},
|
||||||
{"the root's files still", merge([]string{"tsconfig.json"}, false), "gitea,keycloak"},
|
{"a root file beside a module's", merge([]string{"merge-check.sh", "modules/keycloak/x.ts"}, false), "keycloak"},
|
||||||
} {
|
} {
|
||||||
if got := named(whatTheMergeTouched(candidates, known, c.m)); got != c.want {
|
if got := named(whatTheMergeTouched(candidates, known, c.m)); got != c.want {
|
||||||
t.Errorf("%s: rebuilt %q, wanted %q", c.what, got, c.want)
|
t.Errorf("%s: rebuilt %q, wanted %q", c.what, got, c.want)
|
||||||
@@ -245,12 +245,11 @@ func TestAModuleBuiltAtACommitStillFollowsItsBranch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// novox/hq issue 278: whether a directory is a module is a fact of the repository at the merge commit,
|
// novox/hq issues 278 and 280: a merge touching the code of a module the mesh does not hold — the
|
||||||
// and the announcer says it. A merge touching the code of a module the mesh does not hold — the
|
|
||||||
// catalogue's reference module, whose manifest it left alone — read as shared and rebuilt every module
|
// catalogue's reference module, whose manifest it left alone — read as shared and rebuilt every module
|
||||||
// built from the repository (103 of them on 2026-10-06, 88 byte-identical). Said by the announcer, it
|
// built from the repository (103 of them on 2026-10-06, 88 byte-identical); so did a merge-check.sh added at
|
||||||
// rebuilds nothing; a directory holding no manifest is shared as before; and an announcer that does not
|
// the root. No build reads a file outside its module's directory, so neither rebuilds anything, said by the
|
||||||
// say keeps the old rule.
|
// announcer or not.
|
||||||
func TestAChangeInsideAModuleIsThatModulesHeldOrNot(t *testing.T) {
|
func TestAChangeInsideAModuleIsThatModulesHeldOrNot(t *testing.T) {
|
||||||
const repo = "http://forge.internal:20000/novox/mesh-catalog.git"
|
const repo = "http://forge.internal:20000/novox/mesh-catalog.git"
|
||||||
gitea := fromRepo("gitea", repo, "modules/gitea")
|
gitea := fromRepo("gitea", repo, "modules/gitea")
|
||||||
@@ -278,12 +277,12 @@ func TestAChangeInsideAModuleIsThatModulesHeldOrNot(t *testing.T) {
|
|||||||
{"beside a held one's change", merge(append([]string{"modules/gitea/index.ts"}, showcase...),
|
{"beside a held one's change", merge(append([]string{"modules/gitea/index.ts"}, showcase...),
|
||||||
[]string{"modules/gitea", "modules/showcase"}, true), "gitea"},
|
[]string{"modules/gitea", "modules/showcase"}, true), "gitea"},
|
||||||
{"deeper inside it", merge([]string{"modules/showcase/daemon/index.ts"}, []string{"modules/showcase"}, true), ""},
|
{"deeper inside it", merge([]string{"modules/showcase/daemon/index.ts"}, []string{"modules/showcase"}, true), ""},
|
||||||
{"a directory holding no manifest is still shared", merge([]string{"modules/lib/x.go"}, nil, true), "gitea,keycloak"},
|
{"a directory holding no manifest, read by no build", merge([]string{"modules/lib/x.go"}, nil, true), ""},
|
||||||
{"one of two files in no module", merge([]string{"modules/showcase/index.ts", "modules/lib/x.go"},
|
{"one of two files in no module", merge([]string{"modules/showcase/index.ts", "modules/lib/x.go"},
|
||||||
[]string{"modules/showcase"}, true), "gitea,keycloak"},
|
[]string{"modules/showcase"}, true), ""},
|
||||||
{"the root is never a module directory", merge([]string{"tsconfig.json"}, []string{"", "/", "."}, true), "gitea,keycloak"},
|
{"the root is never a module directory", merge([]string{"tsconfig.json"}, []string{"", "/", "."}, true), ""},
|
||||||
{"not said: the old rule", merge(showcase, []string{"modules/showcase"}, false), "gitea,keycloak"},
|
{"not said: still no build reads it", merge(showcase, []string{"modules/showcase"}, false), ""},
|
||||||
{"an old announcer saying nothing", merge(showcase, nil, false), "gitea,keycloak"},
|
{"an old announcer saying nothing", merge(showcase, nil, false), ""},
|
||||||
{"a manifest the merge removed, said or not", merge([]string{"modules/gone/module.json", "modules/gone/x.ts"}, nil, true), ""},
|
{"a manifest the merge removed, said or not", merge([]string{"modules/gone/module.json", "modules/gone/x.ts"}, nil, true), ""},
|
||||||
} {
|
} {
|
||||||
if got := named(whatTheMergeTouched(candidates, known, c.m)); got != c.want {
|
if got := named(whatTheMergeTouched(candidates, known, c.m)); got != c.want {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -616,65 +615,33 @@ func lastLookAt(entries []inventory.Entry, m link.SourceMoved) time.Time {
|
|||||||
return newest
|
return newest
|
||||||
}
|
}
|
||||||
|
|
||||||
// whatTheMergeTouched narrows the modules built from a repository to the ones the merge changed.
|
// whatTheMergeTouched narrows the modules built from a repository to the ones the merge changed: **a
|
||||||
|
// changed file touches exactly the modules whose build reads it** (novox/hq issue 280, ADR 0237 as amended).
|
||||||
//
|
//
|
||||||
// **A change inside no module's own directory is a change to what they share.** The forge lists the
|
// What a build reads is the module's own directory — the builder clones the repository and builds within
|
||||||
// files a merge changed; a module is affected when one of them is inside its own directory, when it
|
// that directory alone: the manifest, the recipes, the bundles' sources, the Docker context — or the whole
|
||||||
// is built from the repository's root — everything there is its source — or when some changed file
|
// repository for a module built from its root. A second repository a recipe packages (an artifact's
|
||||||
// belongs to no module's directory at all, which is how a shared file, a build recipe or a
|
// `context`) is read too, and that is the build record's `read`, answered by readsFrom beside this. So a
|
||||||
// dependency at the root rebuilds everything built from that repository.
|
// changed file inside a module's directory is that module's; a file in no module's directory — a script
|
||||||
|
// at the root, a README, CI configuration, a directory of notes — is read by no build and touches nothing.
|
||||||
//
|
//
|
||||||
// A change inside *another* module's directory is that module's business and not this one's, even
|
// **It used to be read as shared code**, rebuilding everything built from the repository, on the theory
|
||||||
// when the mesh does not hold that module: `known` is every module this repository is known to hold,
|
// that a root file might be a build input (04-ISSUES/131). No build reads one: the theory cost a rebuild
|
||||||
// whatever branch it was registered from.
|
// of 103 modules for a merge-check.sh added at the catalogue's root (issue 280), as it had for a module
|
||||||
|
// held by no machine (278) and a new module's directory (252), each patched as an exception to a rule that
|
||||||
|
// was wrong. Rebuilding too much is not the safe direction when every rebuild is a rollout.
|
||||||
//
|
//
|
||||||
// **And a module the mesh has never seen is still a module** (novox/hq issue 252). A merge adding a
|
// Nothing said about the files, or not all of them said, is still everything: what is not known cannot be
|
||||||
// new module to the catalogue repository — `modules/newmod/module.json` and its files — read as a
|
// narrowed. `known` is kept for the callers; which directories hold a module is no longer needed to read a
|
||||||
// change to shared code, because `modules/newmod` was nobody's known directory, and every module
|
// change, because a file is a module's only by being inside it.
|
||||||
// built from the repository was rebuilt and rolled out for a module none of them is. So the
|
|
||||||
// directories that hold modules are known too: the parents of the known modules' directories
|
|
||||||
// (`modules`, never the root). A changed path `<parent>/<name>/…` belongs to the module at
|
|
||||||
// `<parent>/<name>` — held or not — and rebuilds nothing else, **provided it is shown to be a
|
|
||||||
// module**: its `module.json` is among the changed files (added, changed, or removed with it). A
|
|
||||||
// directory under the same parent whose manifest the merge did not touch may as well be a shared
|
|
||||||
// library (`modules/lib`), and that is still read as shared. Rebuilding too much remains the safe
|
|
||||||
// direction: the fault this whole path exists for is a mesh that believes it is current and is not
|
|
||||||
// (novox/hq 04-ISSUES/131). A file at the root, or directly in a parent, is shared as it always was.
|
|
||||||
//
|
|
||||||
// **Whether a directory is a module is a fact of the repository at the merge commit** (novox/hq issue
|
|
||||||
// 278), and the announcer now says it: a changed file inside a directory holding a `module.json` there
|
|
||||||
// is that module's, whatever the merge did to the manifest; only a file in no such directory is shared.
|
|
||||||
// From an announcer that does not say, the rule above stands.
|
|
||||||
func whatTheMergeTouched(candidates, known []inventory.Entry, m link.SourceMoved) []inventory.Entry {
|
func whatTheMergeTouched(candidates, known []inventory.Entry, m link.SourceMoved) []inventory.Entry {
|
||||||
// Nothing said about the files, or not all of them said: everything built from it is affected.
|
_ = known
|
||||||
if len(m.Paths) == 0 || m.PathsTruncated {
|
if len(m.Paths) == 0 || m.PathsTruncated {
|
||||||
return candidates
|
return candidates
|
||||||
}
|
}
|
||||||
var dirs []string
|
|
||||||
parents := map[string]bool{}
|
|
||||||
for _, e := range known {
|
|
||||||
if e.Source.Path != "" && sameRepository(e.Source.Repository, m) {
|
|
||||||
dir := strings.Trim(e.Source.Path, "/")
|
|
||||||
dirs = append(dirs, dir)
|
|
||||||
if parent := path.Dir(dir); parent != "." && parent != "/" {
|
|
||||||
parents[parent] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changed := map[string]bool{}
|
|
||||||
for _, p := range m.Paths {
|
|
||||||
changed[strings.TrimPrefix(p, "/")] = true
|
|
||||||
}
|
|
||||||
modules := saidModuleDirs(m)
|
|
||||||
for _, p := range m.Paths {
|
|
||||||
if insideAny(p, dirs) || inAModuleOfItsOwn(p, parents, changed) || insideAny(p, modules) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return candidates
|
|
||||||
}
|
|
||||||
var out []inventory.Entry
|
var out []inventory.Entry
|
||||||
for _, e := range candidates {
|
for _, e := range candidates {
|
||||||
if e.Source.Path == "" || anyInside(m.Paths, e.Source.Path) {
|
if strings.Trim(e.Source.Path, "/") == "" || anyInside(m.Paths, e.Source.Path) {
|
||||||
out = append(out, e)
|
out = append(out, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -700,28 +667,6 @@ func saidModuleDirs(m link.SourceMoved) []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// inAModuleOfItsOwn is whether a changed file is inside a module directory the mesh does not know —
|
|
||||||
// `<parent>/<name>/…` under a directory known to hold modules, whose `module.json` the same merge
|
|
||||||
// changed (novox/hq issue 252). Such a file is that module's business and nobody else's.
|
|
||||||
func inAModuleOfItsOwn(p string, parents, changed map[string]bool) bool {
|
|
||||||
p = strings.TrimPrefix(p, "/")
|
|
||||||
for parent := range parents {
|
|
||||||
rest, under := strings.CutPrefix(p, parent+"/")
|
|
||||||
if !under {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name, _, inADirectory := strings.Cut(rest, "/")
|
|
||||||
if !inADirectory || name == "" {
|
|
||||||
// A file directly in the parent — `modules/README.md` — is about all of them.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if changed[parent+"/"+name+"/module.json"] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// inside is whether a changed file is in a directory: that directory itself, or under it.
|
// inside is whether a changed file is in a directory: that directory itself, or under it.
|
||||||
func inside(path, dir string) bool {
|
func inside(path, dir string) bool {
|
||||||
dir = strings.Trim(dir, "/")
|
dir = strings.Trim(dir, "/")
|
||||||
|
|||||||
@@ -223,8 +223,8 @@ type PullUpdated struct {
|
|||||||
PathsTruncated bool `json:"paths_truncated,omitempty"`
|
PathsTruncated bool `json:"paths_truncated,omitempty"`
|
||||||
// ModuleDirs are the directories above the changed files that hold a `module.json` at the head, read
|
// ModuleDirs are the directories above the changed files that hold a `module.json` at the head, read
|
||||||
// as the merge announcer reads them at a merge commit (issue 278); ModuleDirsSaid says it looked.
|
// as the merge announcer reads them at a merge commit (issue 278); ModuleDirsSaid says it looked.
|
||||||
// What the controller maps a pull request onto the mesh's module graph with: a file inside one is
|
// What the controller finds a module the graph does not hold yet with: a directory the change adds a
|
||||||
// that module's, held or not, and one inside none, in a repository modules are built from, is shared.
|
// module.json in is a new module, checked before it merges.
|
||||||
ModuleDirs []string `json:"module_dirs,omitempty"`
|
ModuleDirs []string `json:"module_dirs,omitempty"`
|
||||||
ModuleDirsSaid bool `json:"module_dirs_said,omitempty"`
|
ModuleDirsSaid bool `json:"module_dirs_said,omitempty"`
|
||||||
// MergeCheck says the head holds a merge-check.sh at its root — the repository's own tests, the
|
// MergeCheck says the head holds a merge-check.sh at its root — the repository's own tests, the
|
||||||
|
|||||||
Reference in New Issue
Block a user