Ask the planner what a pull request reaches; map a changed file onto modules in one place (hq ADR 0238)
touchedBy is now the only mapping of changed files onto modules — touched, added, and read by no build — and reachOfMerge the planner's whole answer with the dependency walk. The merge handler, the plan what-if, the merge gate's width and composition, and a pull request's check all ask it, so planning and gating cannot disagree. The gate composes the definitions of the modules a merge would rebuild or add, not every one in the tree, and the check says the dependents a merge would build after them.
This commit is contained in:
@@ -22,7 +22,8 @@ import (
|
||||
//
|
||||
// **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 rule the merge handler and the release planner use (whatTheMergeTouched, issue 280): **a changed file
|
||||
// the planner's own answer (reachOfMerge, touchedBy — the one place a changed file is mapped onto modules,
|
||||
// for the merge handler, the release planner, the merge gate and this check; issue 280): **a changed file
|
||||
// touches exactly the modules whose build reads it** — a module's own directory (the whole repository for
|
||||
// one built from its root), or a repository its recipe packages. A file no build reads — a script at the
|
||||
// root, a README — touches no module. A directory the change adds a module.json in, which the graph does
|
||||
@@ -52,14 +53,20 @@ const noMergeCheck = "the repository declares no merge-check.sh: none of its own
|
||||
var coreModules = map[string]string{"mesh-controller": "mesh-controller", "mesh-host": "mesh-host",
|
||||
"node-tools": "mesh-tools", "nats": "mesh-catalog"}
|
||||
|
||||
// checkScope is what a pull request touches of the mesh's graph.
|
||||
// checkScope is what a pull request reaches of the mesh's graph, as the planner reckons it.
|
||||
type checkScope struct {
|
||||
// Modules are the graph's modules built from the repository into the pull request's base that the
|
||||
// change touches, sorted; New the directories it adds a module in that the graph does not hold.
|
||||
Modules []string
|
||||
New []string
|
||||
// Manifests are their manifests in the change's tree.
|
||||
// Modules are the modules a merge of the change would move itself — built from the repository into
|
||||
// the pull request's base and reading a changed file, or packaging the repository's source — and
|
||||
// Dependents those the plan would build after them; New the directories it adds a module in.
|
||||
Modules []string
|
||||
Dependents []string
|
||||
New []string
|
||||
// Manifests are the moved modules' and the new ones' manifests in the change's tree.
|
||||
Manifests []string
|
||||
// Width is how many modules a merge would build, in how many tiers; Unread the changed files no
|
||||
// module's build reads.
|
||||
Width, Tiers int
|
||||
Unread []string
|
||||
// Mesh says the repository is the mesh's: modules are built from it on some branch, its owner is the
|
||||
// core's, or the change adds a module to it.
|
||||
Mesh bool
|
||||
@@ -71,13 +78,35 @@ type checkScope struct {
|
||||
|
||||
func (s checkScope) gated() bool { return len(s.Modules)+len(s.New) > 0 }
|
||||
|
||||
// pullScope maps a pull request onto the mesh's module graph.
|
||||
func pullScope(p link.PullUpdated, entries []inventory.Entry, read map[string][]inventory.ReadRepository) checkScope {
|
||||
// pullScope is what a pull request reaches: **the planner's own answer** (reachOfMerge), asked as if the
|
||||
// head were merged into the base — never a mapping of its own, so a change to what a merge touches
|
||||
// changes what is checked with it (novox/hq ADR 0238).
|
||||
func pullScope(p link.PullUpdated, entries []inventory.Entry, read map[string][]inventory.ReadRepository,
|
||||
edges []inventory.Edge) checkScope {
|
||||
m := link.SourceMoved{Owner: p.Owner, Repo: p.Repo, Base: p.Base, CloneURL: p.CloneURL, Commit: p.Commit,
|
||||
Paths: p.Paths, PathsTruncated: p.PathsTruncated, ModuleDirs: p.ModuleDirs, ModuleDirsSaid: p.ModuleDirsSaid}
|
||||
var s checkScope
|
||||
var from []inventory.Entry
|
||||
known := map[string]bool{}
|
||||
Paths: p.Paths, PathsTruncated: p.PathsTruncated, Removed: p.Removed, ModuleDirs: p.ModuleDirs,
|
||||
ModuleDirsSaid: p.ModuleDirsSaid}
|
||||
r := reachOfMerge(m, entries, read, edges)
|
||||
s := checkScope{Modules: r.Moved(), Dependents: r.Dependents(), New: r.Added, Unread: r.Unread,
|
||||
Width: len(r.Plan.Modules), Tiers: len(r.Plan.Tiers)}
|
||||
for _, e := range append(append([]inventory.Entry{}, r.Touched...), r.Deleted...) {
|
||||
s.Manifests = append(s.Manifests, path.Join(strings.Trim(e.Source.Path, "/"), moduleManifestFile))
|
||||
switch e.Manifest.Module {
|
||||
case "mesh-controller":
|
||||
s.Judge = link.JudgeSelf
|
||||
case "mesh-host":
|
||||
if s.Judge == "" {
|
||||
s.Judge = link.JudgeValidator
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, d := range r.Added {
|
||||
s.Manifests = append(s.Manifests, path.Join(d, moduleManifestFile))
|
||||
}
|
||||
sort.Strings(s.Manifests)
|
||||
s.Manifests = slices.Compact(s.Manifests)
|
||||
|
||||
// Whose repository it is, for how it is cloned and whether its own check is the mesh's to run.
|
||||
owners := map[string]bool{}
|
||||
for i, e := range entries {
|
||||
if e.Provided {
|
||||
@@ -88,77 +117,11 @@ func pullScope(p link.PullUpdated, entries []inventory.Entry, read map[string][]
|
||||
owners[owner] = true
|
||||
}
|
||||
}
|
||||
if !sameRepository(e.Source.Repository, m) {
|
||||
continue
|
||||
}
|
||||
s.Mesh = true
|
||||
known[strings.Trim(e.Source.Path, "/")] = true
|
||||
if s.From == nil {
|
||||
if sameRepository(e.Source.Repository, m) && s.From == nil {
|
||||
s.From = &entries[i]
|
||||
}
|
||||
if sourceIs(e.Source, m) {
|
||||
from = append(from, e)
|
||||
}
|
||||
}
|
||||
touched := map[string]inventory.Entry{}
|
||||
if len(from) > 0 {
|
||||
for _, e := range whatTheMergeTouched(from, entries, m) {
|
||||
touched[e.Manifest.Module] = e
|
||||
}
|
||||
}
|
||||
// A module whose build packages source from this repository's branch is touched by any change to it.
|
||||
for _, e := range entries {
|
||||
if !e.Provided && readsFrom(read[e.Manifest.Module], m) {
|
||||
touched[e.Manifest.Module] = e
|
||||
}
|
||||
}
|
||||
for name, e := range touched {
|
||||
s.Modules = append(s.Modules, name)
|
||||
if sameRepository(e.Source.Repository, m) {
|
||||
s.Manifests = append(s.Manifests, path.Join(strings.Trim(e.Source.Path, "/"), "module.json"))
|
||||
}
|
||||
switch name {
|
||||
case "mesh-controller":
|
||||
s.Judge = link.JudgeSelf
|
||||
case "mesh-host":
|
||||
if s.Judge == "" {
|
||||
s.Judge = link.JudgeValidator
|
||||
}
|
||||
}
|
||||
}
|
||||
// A module the graph does not hold yet, in a directory the head says holds one — or at the root.
|
||||
for _, d := range saidModuleDirs(m) {
|
||||
if !known[d] {
|
||||
s.New = append(s.New, d)
|
||||
s.Manifests = append(s.Manifests, d+"/module.json")
|
||||
}
|
||||
}
|
||||
// And, said or not, a manifest the change adds or moves in a directory the graph does not know.
|
||||
for _, changed := range p.Paths {
|
||||
changed = strings.Trim(changed, "/")
|
||||
if path.Base(changed) != "module.json" {
|
||||
continue
|
||||
}
|
||||
d := path.Dir(changed)
|
||||
if d == "." {
|
||||
d = ""
|
||||
}
|
||||
if !known[d] {
|
||||
if d == "" {
|
||||
d = "."
|
||||
}
|
||||
s.New = append(s.New, d)
|
||||
s.Manifests = append(s.Manifests, changed)
|
||||
}
|
||||
}
|
||||
sort.Strings(s.Modules)
|
||||
sort.Strings(s.New)
|
||||
s.New = slices.Compact(s.New)
|
||||
sort.Strings(s.Manifests)
|
||||
s.Manifests = slices.Compact(s.Manifests)
|
||||
if owners[strings.ToLower(p.Owner)] || s.gated() {
|
||||
s.Mesh = true
|
||||
}
|
||||
s.Mesh = s.From != nil || owners[strings.ToLower(p.Owner)] || s.gated()
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -182,7 +145,11 @@ func (f following) PullUpdated(ctx context.Context, p link.PullUpdated) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scope := pullScope(p, entries, read)
|
||||
edges, err := inv.Dependencies(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scope := pullScope(p, entries, read, edges)
|
||||
direct := link.Checked{Owner: p.Owner, Repo: p.Repo, Number: p.Number, Commit: p.Commit,
|
||||
ID: link.NewBuildID(time.Now()), Verdict: "pass", Summary: noModuleTouched,
|
||||
Gate: &link.CheckLayer{Verdict: "pass", Summary: noModuleTouched}}
|
||||
@@ -213,7 +180,9 @@ func (f following) PullUpdated(ctx context.Context, p link.PullUpdated) error {
|
||||
}
|
||||
what := "its own merge-check.sh alone: " + noModuleTouched
|
||||
if scope.gated() {
|
||||
what = "the gate over " + strings.Join(append(append([]string{}, scope.Modules...), scope.New...), ", ")
|
||||
what = fmt.Sprintf("the gate over %s (a merge would build %d module(s) in %d tier(s))",
|
||||
strings.Join(append(append([]string{}, scope.Modules...), prefixedAll("new:", scope.New)...), ", "),
|
||||
scope.Width, scope.Tiers)
|
||||
}
|
||||
fmt.Printf("%s/%s#%d (%.8s): asked %s to check it before it merges — %s — as %s\n", p.Owner, p.Repo, p.Number,
|
||||
p.Commit, seat, what, request.ID)
|
||||
@@ -289,8 +258,8 @@ func checkRequestFor(ctx context.Context, open *stores, p link.PullUpdated, scop
|
||||
Seats: seatBases(ctx),
|
||||
Source: sourceOnSeat(source),
|
||||
Check: &link.CheckRequest{Owner: p.Owner, Repo: p.Repo, Number: p.Number, Base: p.Base,
|
||||
Paths: p.Paths, Beside: beside, Modules: scope.Modules, New: scope.New, Manifests: scope.Manifests,
|
||||
Judge: scope.Judge},
|
||||
Paths: p.Paths, Beside: beside, Modules: scope.Modules, Dependents: scope.Dependents, New: scope.New,
|
||||
Manifests: scope.Manifests, Judge: scope.Judge},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -354,6 +323,9 @@ func checkedOf(result link.BuildResult) link.Checked {
|
||||
if result.Checked != nil && c.Gate != nil && len(c.Gate.Modules) == 0 {
|
||||
c.Gate.Modules = append(append([]string{}, result.Checked.Modules...), prefixedAll("new:", result.Checked.New)...)
|
||||
}
|
||||
if result.Checked != nil && c.Gate != nil && len(c.Gate.Dependents) == 0 {
|
||||
c.Gate.Dependents = result.Checked.Dependents
|
||||
}
|
||||
for _, l := range []*link.CheckLayer{c.Gate, c.RepoCheck} {
|
||||
if l != nil && l.Verdict == "" {
|
||||
l.Verdict = "error"
|
||||
|
||||
Reference in New Issue
Block a user